面試題: 判斷字符串是否在另外一個字符串中存在?
面試時發現好多人回答很差, 因此就梳理了一下已知的方法, 此文較長, 須要耐心的看下去。從實現和算法原理兩方面解此問題, 其中有用PHP原生方法實現也有一些業界大牛創造的算法。php
<?php /* strpos示例 */ // test echo 'match:', strpos('xasfsdfbk', 'xasfsdfbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', strpos('xasfsdfbk', 'fbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', strpos('xasfsdfbk', 'xs') != false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', strpos('xasfsdfbk', 'sfs') !== false ? 'true' : 'false', ';', PHP_EOL; // code strpos('xasfsdfbk', 'sfs'); // mb* 相關的函數也可, 好比說mb_strpos是基於字符數執行一個多字節安全的 strpos() 操做。
函數 | 描述 | 版本 |
---|---|---|
strpos | 查找字符串首次出現的位置 | PHP 4, PHP 5, PHP 7 |
stripos | 查找字符串首次出現的位置(不區分大小寫) | PHP 5, PHP 7 |
strrpos | 計算指定字符串在目標字符串中最後一次出現的位置 | PHP 4, PHP 5, PHP 7 |
strripos | 計算指定字符串在目標字符串中最後一次出現的位置(不區分大小寫) | PHP 5, PHP 7 |
mb_strpos | 查找字符串在另外一個字符串中首次出現的位置 | PHP 4 >= 4.0.6, PHP 5, PHP 7 |
strstr | 查找字符串的首次出現 | PHP 4, PHP 5, PHP 7 |
stristr | strstr() 函數的忽略大小寫版本 | PHP 4, PHP 5, PHP 7 |
substr_count | 計算字串出現的次數 | PHP 4, PHP 5, PHP 7 |
<?php // test echo 'match:', str_match('xasfsdfbk', 'xasfsdfbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'fbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'xs') != false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'sfs') !== false ? 'true' : 'false', ';', PHP_EOL; // code function str_match($a, $b) { return preg_match('/' . $b . '/i', $a, $matchs) ? true : false; }
函數 | 描述 | 版本 |
---|---|---|
preg_match | 執行匹配正則表達式 | PHP 4, PHP 5, PHP 7 |
preg_match_all | 執行一個全局正則表達式匹配 | PHP 4, PHP 5, PHP 7 |
<?php // test echo 'match:', str_match('xasfsdfbk', 'xasfsdfbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'fbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'xs') != false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'sfs') !== false ? 'true' : 'false', ';', PHP_EOL; // code function str_match($a, $b) { return count(explode($b, $a)) >= 2 ? true : false; } // strtok 能夠麼? // 在分割字符串時,split()與explode()誰快?
函數 | 描述 | 版本 |
---|---|---|
strtok | 標記分割字符串 | PHP 4, PHP 5, PHP 7 |
explode | 使用一個字符串分割另外一個字符串 | PHP 4, PHP 5, PHP 7 |
split | 用正則表達式將字符串分割到數組中 | PHP 4, PHP 5 |
mb_split | 使用正則表達式分割多字節字符串 | PHP 4 >= 4.2.0, PHP 5, PHP 7 |
preg_split | 經過一個正則表達式分隔字符串 | PHP 4, PHP 5, PHP 7 |
<?php // test echo 'match:', str_match('xasfsdfbk', 'xasfsdfbk') !== -1 ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'fbk') !== -1 ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'xs') !== -1 ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'sfs') !== -1 ? 'true' : 'false', ';', PHP_EOL; // code function str_match($a, $b) { if($a == $b) { return 0; } $aArr = str_split($a); $bArr = str_split($b); $aLen = count($aArr); $bLen = count($bArr); if($bLen > $aLen) { return -1; } $data = []; $aLastIndex = -1; $bStartIndex = 0; for($ai = 0; $ai < $aLen; $ai++) { $av = $aArr[$ai]; $exists = false; for($bi = $bStartIndex; $bi < $bLen; $bi++) { $bv = $bArr[$bi]; if(($aLastIndex == -1 || $ai == ($aLastIndex + 1)) && $av == $bv) { $exists = true; break; } if ($aLastIndex != -1 && $ai == ($aLastIndex + 1) && $av != $bv) { break; } } if ($exists) { $aLastIndex = $ai; $bStartIndex = $bi + 1; array_push($data, [ 'value' => $av, 'index' => $ai ]); } else { $aLastIndex = -1; $bStartIndex = 0; $data = []; } if ($exists && $bLen == $bStartIndex) { break; } } if(!empty($data) && count($data) == $bLen) { $begin = array_shift($data); return $begin['index']; } else { return -1; } }
<?php // demo echo 'match:', str_match('xasfsdfbk', 'xasfsdfbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'fbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'xs') != false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'sfs') !== false ? 'true' : 'false', ';', PHP_EOL; // code function str_match($a, $b) { $aArr = str_split($a); $bArr = str_split($b); $aLen = count($aArr); $bLen = count($bArr); for ($ai = 0; $ai <= $aLen - $bLen; $ai++) { for ($bi = 0; $bi < $bLen; $bi++) { if($aArr[$ai + $bi] != $bArr[$bi]) { break; } } if($bLen == $bi) { return $ai; } } return false; }
#include <iostream> #include <string.h> using namespace std; #define BASE 256 #define MODULUS 101 void RabinKarp(char t[], char p[]) { int t_len = strlen(t); int p_len = strlen(p); // 哈希滾動之用 int h = 1; for (int i = 0; i < p_len - 1; i++) h = (h * BASE) % MODULUS; int t_hash = 0; int p_hash = 0; for (int i = 0; i < p_len; i++) { t_hash = (BASE * t_hash + t[i]) % MODULUS; p_hash = (BASE * p_hash + p[i]) % MODULUS; } int i = 0; while (i <= t_len - p_len) { // 考慮到哈希碰撞的可能性,還須要用 memcmp 再比對一下 if (t_hash == p_hash && memcmp(p, t + i, p_len) == 0) cout << p << " is found at index " << i << endl; // 哈希滾動 t_hash = (BASE * (t_hash - t[i] * h) + t[i + p_len]) % MODULUS; // 防止出現負數 if (t_hash < 0) t_hash = t_hash + MODULUS; i++; } } int main() { char t[100] = "It is a test, but not just a test"; char p[10] = "test"; RabinKarp(t, p); return 0; }
<?php // php 實現 function hash_string($str, $len) { $hash = ''; $hash_table = array( 'h' => 1, 'e' => 2, 'l' => 3, 'o' => 4, 'w' => 5, 'r' => 6, 'd' => 7, ); for ($i = 0; $i < $len; $i++) { $hash .= $hash_table[$str{$i}]; } return (int)$hash; } function rabin_karp($text, $pattern) { $n = strlen($text); $m = strlen($pattern); $text_hash = hash_string(substr($text, 0, $m), $m); $pattern_hash = hash_string($pattern, $m); for ($i = 0; $i < $n-$m+1; $i++) { if ($text_hash == $pattern_hash) { return $i; } $text_hash = hash_string(substr($text, $i, $m), $m); } return -1; } // 2 echo rabin_karp('hello world', 'ello');
public class KMP { public static int KMPSearch(String txt, String pat, int[] next) { int M = txt.length(); int N = pat.length(); int i = 0; int j = 0; while (i < M && j < N) { if (j == -1 || txt.charAt(i) == pat.charAt(j)) { i++; j++; } else { j = next[j]; } } if (j == N) return i - j; else return -1; } public static void getNext(String pat, int[] next) { int N = pat.length(); next[0] = -1; int k = -1; int j = 0; while (j < N - 1) { if (k == -1 || pat.charAt(j) == pat.charAt(k)) { ++k; ++j; next[j] = k; } else k = next[k]; } } public static void main(String[] args) { String txt = "BBC ABCDAB CDABABCDABCDABDE"; String pat = "ABCDABD"; int[] next = new int[pat.length()]; getNext(pat, next); System.out.println(KMPSearch(txt, pat, next)); } }
public class BoyerMoore { public static void getRight(String pat, int[] right) { for (int i = 0; i < 256; i++){ right[i] = -1; } for (int i = 0; i < pat.length(); i++) { right[pat.charAt(i)] = i; } } public static int BoyerMooreSearch(String txt, String pat, int[] right) { int M = txt.length(); int N = pat.length(); int skip; for (int i = 0; i <= M - N; i += skip) { skip = 0; for (int j = N - 1; j >= 0; j--) { if (pat.charAt(j) != txt.charAt(i + j)) { skip = j - right[txt.charAt(i + j)]; if (skip < 1){ skip = 1; } break; } } if (skip == 0) return i; } return -1; } public static void main(String[] args) { String txt = "BBC ABCDAB AACDABABCDABCDABDE"; String pat = "ABCDABD"; int[] right = new int[256]; getRight(pat,right); System.out.println(BoyerMooreSearch(txt, pat, right)); } }
public class Sunday { public static int getIndex(String pat, Character c) { for (int i = pat.length() - 1; i >= 0; i--) { if (pat.charAt(i) == c) return i; } return -1; } public static int SundaySearch(String txt, String pat) { int M = txt.length(); int N = pat.length(); int i, j; int skip = -1; for (i = 0; i <= M - N; i += skip) { for (j = 0; j < N; j++) { if (txt.charAt(i + j) != pat.charAt(j)){ if (i == M - N) break; skip = N - getIndex(pat, txt.charAt(i + N)); break; } } if (j == N) return i; } return -1; } public static void main(String[] args) { String txt = "BBC ABCDAB AACDABABCDABCDABD"; String pat = "ABCDABD"; System.out.println(SundaySearch(txt, pat)); } }
#include <stdio.h> #include <stdlib.h> #include <string.h> int index_bf(char *s,char *t,int pos); int index_bf_self(char *s,char *t,int index); int main() { char s[]="6he3wor"; //標準BF算法中,s[0]和t[0]存放的爲字符串長度。 char t[]="3wor"; int m=index_bf(s,t,2); //標準BF算法 printf("index_bf:%d\n",m); m=index_bf_self(s,t,2); //修改版BF算法,s和t中,沒必要再存放字符串長度。 printf("index_bf_self:%d\n",m); exit(0); } /* 字符串S和T中,s[0],t[0]存放必須爲字符串長度 例:s[]="7hi baby!" T[]="4baby" index_bf(s,t,1); pos:在S中要從下標pos處開始查找T (說明:標準BF算法中,爲研究方便,s[0],t[0]中存放的爲各自字符串長度。) */ int index_bf(char *s,char *t,int pos) { int i,j; if(pos>=1 && pos <=s[0]-'0') { i=pos; j=1; while(i<=s[0]-'0'&&j<=t[0]-'0') { if(s[i]==t[j]) { i++; j++; } else { j=1; i=i-j+2; } if(j>t[0]-'0') { return i-t[0]+'0'; } } return -1; } else { return -1; } } /* 修改版,字符串s和t中,沒必要再包含字符串長度。 例:s[]="hi baby" t[]="baby" index_bf_self(s,t,0); index:在s中,從幾號下標開始查找 */ int index_bf_self(char *s,char *t,int index) { int i=index,j=0; while(s[i]!='\0') { while(*(t+j)!='\0' && *(s+i+j)!='\0') { if(*(t+j)!=*(s+i+j)) break; j++; } if(*(t+j)=='\0') { return i; } i++; j=0; } return -1; }
//////////////////////////////////////////////////// /* 程序說明:多模式串匹配的AC自動機算法 自動機算法能夠參考《柔性字符串匹配》裏的相應章節,講的很清楚 */ #include <stdio.h> #include <string.h> const int MAXQ = 500000+10; const int MAXN = 1000000+10; const int MAXK = 26; //自動機裏字符集的大小 struct TrieNode { TrieNode* fail; TrieNode* next[MAXK]; bool danger; //該節點是否爲某模式串的終結點 int cnt; //以該節點爲終結點的模式串個數 TrieNode() { fail = NULL; memset(next, NULL, sizeof(next)); danger = false; cnt = 0; } }*que[MAXQ], *root; //文本字符串 char msg[MAXN]; int N; void TrieInsert(char *s) { int i = 0; TrieNode *ptr = root; while(s[i]) { int idx = s[i]-'a'; if(ptr->next[idx] == NULL) ptr->next[idx] = new TrieNode(); ptr = ptr->next[idx]; i++; } ptr->danger = true; ptr->cnt++; } void Init() { int i; char s[100]; root = new TrieNode(); scanf("%d", &N); for(i = 0; i < N; i++) { scanf("%s", s); TrieInsert(s); } } void Build_AC_Automation() { int rear = 1, front = 0, i; que[0] = root; root->fail = NULL; while(rear != front) { TrieNode *cur = que[front++]; for(i = 0; i < 26; i++) if(cur->next[i] != NULL) { if(cur == root) cur->next[i]->fail = root; else { TrieNode *ptr = cur->fail; while(ptr != NULL) { if(ptr->next[i] != NULL) { cur->next[i]->fail = ptr->next[i]; if(ptr->next[i]->danger == true) cur->next[i]->danger = true; break; } ptr = ptr->fail; } if(ptr == NULL) cur->next[i]->fail = root; } que[rear++] = cur->next[i]; } } } int AC_Search() { int i = 0, ans = 0; TrieNode *ptr = root; while(msg[i]) { int idx = msg[i]-'a'; while(ptr->next[idx] == NULL && ptr != root) ptr = ptr->fail; ptr = ptr->next[idx]; if(ptr == NULL) ptr = root; TrieNode *tmp = ptr; while(tmp != NULL && tmp->cnt != -1) { ans += tmp->cnt; tmp->cnt = -1; tmp = tmp->fail; } i++; } return ans; } int main() { int T; scanf("%d", &T); while(T--) { Init(); Build_AC_Automation(); //文本 scanf("%s", msg); printf("%d\n", AC_Search()); } return 0; }
/* 字符串轉數組, 取交集, 判斷結果 */ // demo echo 'match:', str_match('xasfsdfbk', 'xasfsdfbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'fbk') !== false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'xs') != false ? 'true' : 'false', ';', PHP_EOL; echo 'match:', str_match('xasfsdfbk', 'sfs') !== false ? 'true' : 'false', ';', PHP_EOL; // code function str_match($a, $b) { $aArr = str_split($a); $bArr = str_split($b); return join('', array_intersect($aArr, $bArr)) == $b; } // 集合中的元素具備惟一性, 被匹配的字符串中有相同的字符, 將會去重 // 不能保證交集後的元素順序連續
Rabin-Karp 算法(也能夠叫 Karp-Rabin 算法),由 Richard M. Karp 和 Michael O. Rabin 在 1987 年發表,它也是用來解決多模式串匹配問題的。html
它的實現方式有點不同凡響,首先是計算兩個字符串的哈希值,而後經過比較這兩個哈希值的大小來判斷是否出現匹配。java
選擇一個合適的哈希函數很重要。假設文本串爲t[0, n)
,模式串爲p[0, m)
,其中 0<m<n,Hash(t[i,j])
表明字符串t[i, j]
的哈希值。ios
當 Hash(t[0, m-1])!=Hash(p[0,m-1])
時,咱們很天然的會把 Hash(t[1, m])
拿過來繼續比較。在這個過程當中,若咱們從新計算字符串t[1, m]
的哈希值,還須要 O(n)
的時間複雜度,不划算。觀察到字符串t[0, m-1]
與t[1, m]
中有 m-1
個字符是重合的,所以咱們能夠選用滾動哈希函數,那麼從新計算的時間複雜度就降爲 O(1)
。git
Rabin-Karp 算法選用的滾動哈希函數主要是利用 Rabin fingerprint 的思想,舉個例子,計算字符串t[0, m - 1]的哈希值的公式以下,github
Hash(t[0,m-1]) = t[0]*bm-1 + t[1]*bm-2 + ... + t[m-1]*b0
其中的 b 是一個常數,在 Rabin-Karp 算法中,咱們通常取值爲 256,由於一個字符的最大值不超過 255。上面的公式還有一個問題,哈希值若是過大可能會溢出,所以咱們還須要對其取模,這個值應該儘量大,且是質數,這樣能夠減少哈希碰撞的機率,在這裏咱們就取 101。面試
則計算字符串t[1, m]的哈希值公式以下,正則表達式
Hash(t[1,m]) = ( Hash(t[0,m−1]) − t[0]∗bm−1 ) ∗ b + t[m]∗b0
如圖, 算法導論上提供的示例圖:
算法
許多算法能夠完成這個任務,Knuth-Morris-Pratt算法(簡稱KMP)是最經常使用的之一。它以三個發明者命名,起頭的那個K就是著名科學家Donald Knuth。數組
這種算法不太容易理解,網上有不少解釋,但讀起來都很費勁。直到讀到Jake Boxer的文章,才真正理解這種算法。下面是阮一峯對KMP算法解釋。
9.已知空格與D不匹配時,前面六個字符"ABCDAB"是匹配的。查表可知,最後一個匹配字符B對應的"部分匹配值"爲2,所以按照下面的公式算出向後移動的位數:
移動位數 = 已匹配的字符數 - 對應的部分匹配值
由於 6 - 2 等於4,因此將搜索詞向後移動4位。
首先,要了解兩個概念:"前綴"和"後綴"。
"前綴"指除了最後一個字符之外,一個字符串的所有頭部組合;"後綴"指除了第一個字符之外,一個字符串的所有尾部組合。
概念 | 描述 | 字符串示例 "bread" |
---|---|---|
前綴 | 除了最後一個字符之外,一個字符串的所有頭部組合 | b, br, bre, brea |
後綴 | 除了第一個字符之外,一個字符串的所有尾部組合 | read, ead, ad, d |
"部分匹配值"就是"前綴"和"後綴"的最長的共有元素的長度。以"ABCDABD"爲例,
字符串 | 前綴 | 後綴 | 共有元素 | 共有元素長度 |
---|---|---|---|---|
A | [] | [] | [] | 0 |
AB | [A] | [B] | [] | 0 |
ABC | [A, AB] | [BC, C] | [] | 0 |
ABCD | [A, AB, ABC] | [BCD, CD, D] | [] | 0 |
ABCDA | [A, AB, ABC, ABCD] | [BCDA, CDA, DA, A] | [A] | 1 |
ABCDAB | [A, AB, ABC, ABCD, ABCDA] | [BCDAB, CDAB, DAB, AB, B] | [AB] | 2 |
ABCDABD | [A, AB, ABC, ABCD, ABCDA, ABCDAB] | [BCDABD, CDABD, DABD, ABD, BD, D] | [] | 0 |
KMP算法並非效率最高的算法,實際採用並很少。各類文本編輯器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法。
Boyer-Moore算法不只效率高,並且構思巧妙,容易理解。1977年,德克薩斯大學的Robert S. Boyer教授和J Strother Moore教授發明了這種算法。
下面是阮一峯根據Moore教授的例子對Boyer-Moore算法的解釋。
這是一個很聰明的想法,由於若是尾部字符不匹配,那麼只要一次比較,就能夠知道前7個字符(總體上)確定不是要找的結果。
咱們看到,"S"與"E"不匹配。這時,"S"就被稱爲"壞字符"(bad character),即不匹配的字符。咱們還發現,"S"不包含在搜索詞"EXAMPLE"之中,這意味着能夠把搜索詞直接移到"S"的後一位。
後移位數 = 壞字符的位置 - 搜索詞中的上一次出現位置
若是"壞字符"不包含在搜索詞之中,則上一次出現位置爲 -1。
以"P"爲例,它做爲"壞字符",出如今搜索詞的第6位(從0開始編號),在搜索詞中的上一次出現位置爲4,因此後移 6 - 4 = 2位。再之前面第二步的"S"爲例,它出如今第6位,上一次出現位置是 -1(即未出現),則整個搜索詞後移 6 - (-1) = 7位。
後移位數 = 好後綴的位置 - 搜索詞中的上一次出現位置
舉例來講,若是字符串"ABCDAB"的後一個"AB"是"好後綴"。那麼它的位置是5(從0開始計算,取最後的"B"的值),在"搜索詞中的上一次出現位置"是1(第一個"B"的位置),因此後移 5 - 1 = 4位,前一個"AB"移到後一個"AB"的位置。
再舉一個例子,若是字符串"ABCDEF"的"EF"是好後綴,則"EF"的位置是5 ,上一次出現的位置是 -1(即未出現),因此後移 5 - (-1) = 6位,即整個字符串移到"F"的後一位。
這個規則有三個注意點:
回到上文的這個例子。此時,全部的"好後綴"(MPLE、PLE、LE、E)之中,只有"E"在"EXAMPLE"還出如今頭部,因此後移 6 - 0 = 6位。
12.能夠看到,"壞字符規則"只能移3位,"好後綴規則"能夠移6位。因此,Boyer-Moore算法的基本思想是,每次後移這兩個規則之中的較大值。
更巧妙的是,這兩個規則的移動位數,只與搜索詞有關,與原字符串無關。所以,能夠預先計算生成《壞字符規則表》和《好後綴規則表》。使用時,只要查表比較一下就能夠了。
Sunday算法由Daniel M.Sunday在1990年提出,它的思想跟BM算法很類似:1
只不過Sunday算法是從前日後匹配,在匹配失敗時關注的是主串中參加匹配的最末位字符的下一位字符。
若是該字符沒有在模式串中出現則直接跳過,即移動位數 = 模式串長度 + 1;
不然,其移動位數 = 模式串長度 - 該字符最右出現的位置(以0開始) = 模式串中該字符最右出現的位置到尾部的距離 + 1。
下面舉個例子說明下Sunday算法。假定如今要在主串」substring searching」中查找模式串」search」。
剛開始時,把模式串與文主串左邊對齊:
結果發如今第2個字符處發現不匹配,不匹配時關注主串中參加匹配的最末位字符的下一位字符,即標粗的字符 i,由於模式串search中並不存在i,因此模式串直接跳過一大片,向右移動位數 = 匹配串長度 + 1 = 6 + 1 = 7,從 i 以後的那個字符(即字符n)開始下一步的匹配,以下圖:
結果第一個字符就不匹配,再看主串中參加匹配的最末位字符的下一位字符,是’r’,它出如今模式串中的倒數第3位,因而把模式串向右移動3位(m - 3 = 6 - 3 = r 到模式串末尾的距離 + 1 = 2 + 1 =3),使兩個’r’對齊,以下:
匹配成功。
回顧整個過程,咱們只移動了兩次模式串就找到了匹配位置,緣於Sunday算法每一步的移動量都比較大,效率很高。
BF算法核心思想是:首先S[1]和T[1]比較,若相等,則再比較S[2]和T[2],一直到T[M]爲止;若S[1]和T[1]不等,則T向右移動一個字符的位置,再依次進行比較。若是存在k,1≤k≤N,且S[k+1…k+M]=T[1…M],則匹配成功;不然失敗。該算法最壞狀況下要進行M(N-M+1)次比較,時間複雜度爲O(MN)。下面結合圖片,解釋一下:
S表明源字符串,T表明咱們要查找的字符串。BF算法能夠表述以下:依次遍歷字符串S,看是否字符串S中含有字符串T。
所以,咱們依次比較S[0] 和T[0]、S[1] 和T[1]、S[2] 和T[2]……S[n]和T[n] ,從圖中咱們可知,S[0]-S[7]和T[0]-T[7]依次相等。當匹配到S[8]和T[8]時,兩個字符不等。根據定義,此時S和T都要回溯,T向右移動一個字符的位置,即S回溯到S[1]的位置,T回溯到T[0]的位置,再從新開始比較。此時,S[1]和T[0]、S[2]和T[1]……若是再次發現不匹配字符,則再次回溯,即S回溯到S[2]的位置,T回到T[0]的位置。循環往復,直到到達S或者T字符串的結尾。若是是到達S串的結尾,則表示匹配失敗,若是是到達T串的結尾,則表示匹配成功。
BF算法優勢:思想簡單,直接,無需對字符串S和T進行預處理。缺點:每次字符不匹配時,都要回溯到開始位置,時間開銷大。
Aho-Corasick算法又叫AC自動機算法,是一種多模式匹配算法。Aho-Corasick算法能夠在目標串查找多個模式串,出現次數以及出現的位置。
Aho-Corasick算法主要是應用有限自動機的狀態轉移來模擬字符的比較,下面對有限狀態機作幾點說明
上圖是由多模式串{he,she,his,hers}構成的一個有限狀態機:
1.該狀態當字符匹配是按實線標註的狀態進行轉換,當全部實線路徑都不知足(即下一個字符都不匹配時)按虛線狀態進行轉換。
2.對ushers匹配過程以下圖所示:
當轉移到紅色結點時表示已經匹配而且得到模式串
Aho-Corasick算法步驟
Aho-Corasick算法和前面的算法同樣都要對模式串進行預處理,預處理主要包括字典樹Tire的構造,構建狀態轉移表(goto),失效函數(failure function),輸出表(Output)。
Aho-Corasick算法包括如下3個步驟
下面3個步驟分別進行介紹
Tire是哈希樹的變種,Tire樹的邊是模式串的字符,結點就是Tire的狀態表,下圖是多模式串{he,she,his,hers}的Tire樹結構:
下面是多模式串{he,she,his,hers}的goto函數,failure函數,output函數
函數 | 結構圖 |
---|---|
goto函數 | |
failure函數 | |
output函數 |
通常而言,好的字符串匹配算法要有如下特色:
這是評價一個字符匹配算法最重要的標準。一般要求字符匹配能以線性速度執行。
序號 | 指標 | 描述 |
---|---|---|
1) | 預處理時間的複雜性 | 有些算法在進行字符串匹配前須要對模式特徵進行預處理 |
2) | 匹配階段的時間複雜性 | 字符串匹配過程當中執行查找操做的時間複雜性,它一般和文本長度及模式長度相關 |
3) | 最壞狀況下的時間複雜性 | 對一個text進行字符模式匹配時,設法下降各算法的最壞狀況下的時間複雜性是目前的研究熱點之一 |
4) | 最好狀況下的時間複雜性 | 對一個text進行字符模式匹配時的最好的可能性。 |
執行預處理和模式匹配不只須要CPU資源還須要內存資源,儘管目前內存的容量較之前大得多,但爲了提升速度,人們常利用特殊硬件。一般,特殊硬件中內存訪問速度很快但容量偏小,這時,佔用資源少的算法將更具優點。