簡單模式比對演算法 BF演算法(Brute-Force,又稱古典的、經典的、樸素的、窮舉的) 帶回溯,速度慢 演算法的匹配過程的複雜度是O(m*n) (n,m分別為目標串和模式串的長度)
演算法設計思想: 將目標串T的第pos個字元和模式P的第1個字元比較,若相等,繼續逐個比較後續字元;若不等,從目標串T的下一字元(pos+1)起,重新與P的第一個字元比較。 直到目標串T的一個連續子串字元序列與模式P相等。傳回值為T中與P匹配的子序列第一個字元的序號,即匹配成功。 否則,匹配失敗,傳回值 0 。
演算法實現代碼:
int Find(char* target, char* pat){//target:目標串 pat:模式串 int i=0,j=0; int lengthP =strlen(pat), lengthT =strlen(target); while(i<=lengthT-lengthP){ j=0; while(target[i]==pat[j]&&j<lengthP){ i++; j++; } if(j==lengthP) return i-j; //串pat掃描完,匹配成功 else i=i-j+1;//不匹配,做下一趟比較 } return –1; }
KMP(Knuth-Morris-Pratt)演算法 避免回溯,匹配速度快 KMP演算法的複雜度是O(n+m)
KMP演算法,是由Knuth,Morris,Pratt共同提出的模式比對演算法,其對於任何模式和目標序列,都可以在線性時間內完成匹配尋找,而不會發生退化,是一個非常優秀的模式比對演算法。
(1)部分匹配表的產生:
首先,要瞭解兩個概念:”首碼”和”尾碼”。”首碼”指除了最後一個字元以外,一個字串的全部頭部組合;”尾碼”指除了第一個字元以外,一個字串的全部尾部組合。
“部分匹配值”就是”首碼”和”尾碼”的最長的共有元素的長度。以”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。
移動位元 = 已匹配的字元數 - 對應的部分匹配值
已知空格與D不匹配時,前面六個字元”ABCDAB”是匹配的。查表可知,最後一個匹配字元B對應的”部分匹配值”為2,因此按照上面的公式算出向後移動的位元:6 - 2 = 4,所以將搜尋字詞向後移動4位。
“部分匹配”的實質是,有時候,字串頭部和尾部會有重複。比如,”ABCDAB”之中有兩個”AB”,那麼它的”部分匹配值”就是2(”AB”的長度)。搜尋字詞移動的時候,第一個”AB”向後移動4位(字串長度-部分匹配值),就可以來到第二個”AB”的位置。
(2)next數組的求解思路:即部分匹配表的求解
通過上文完全可以對KMP演算法的原理有個清晰的瞭解,那麼下一步就是編程實現了。其中最重要的就是如何根據待匹配的模版字串求出對應每一位的最大相同前尾碼的長度。
規則:
(下面說的對稱不是中心對稱,而是中心字元塊對稱,比如不是abccba,而是abcabc這種對稱。) 當前字元的前一個字元的對稱程度為0的時候,只要將當前字元與子串第一個字元進行比較。這個很好理解啊,前面都是0,說明都不對稱了,如果多加了一個字元,要對稱的話最多是當前的和第一個對稱。比如agcta這個裡面t的是0,那麼後面的a的對稱程度只需要看它是不是等於第一個字元a了。 按照這個推理,我們就可以總結一個規律,不僅前面是0呀,如果前面一個字元的next值是1,那麼我們就把當前字元與子串第二個字元進行比較,因為前面的是1,說明前面的字元已經和第一個相等了,如果這個又與第二個相等了,說明對稱程度就是2了。有兩個字元對稱了。比如上面agctag,倒數第二個a的next是1,說明它和第一個a對稱了,接著我們就把最後一個g與第二個g比較,又相等,自然對稱值就累加了,就是2了。 按照上面的推理,如果一直相等,就一直累加,可以一直推啊,推到這裡應該一點難度都沒有吧,如果你覺得有難度說明我寫的太失敗了。當然不可能會那麼順利讓我們一直對稱下去,如果遇到下一個不相等了,那麼說明不能繼承前面的對稱性了,這種情況只能說明沒有那麼多對稱了,但是不能說明一點對稱性都沒有,所以遇到這種情況就要重新來考慮,這個也是痛點所在。
結合代碼理解:
void makeNext(const char P[],int next[]){ int q,k;//q:模版字串下標;k:最大前尾碼長度 int m = strlen(P);//模版字串長度 next[0] = 0;//模版字串的第一個字元的最大前尾碼長度為0 for (q = 1,k = 0; q < m; ++q)//for迴圈,從第二個字元開始,依次計算每一個字元對應的next值 { while(k > 0 && P[q] != P[k])//遞迴地求出P[0]···P[q]的最大的相同的前尾碼長度k k = next[k-1];//這個while迴圈是整段代碼的精髓所在,自己舉個例子試試有助於理解。 if (P[q] == P[k])//如果相等,那麼最大相同前尾碼長度加1 { k++; } next[q] = k;//最大前尾碼長度即為next值 }}
KMP完整代碼:
#include<stdio.h>#include<string.h>void makeNext(const char P[],int next[]){ int q,k; int m = strlen(P); next[0] = 0; for (q = 1,k = 0; q < m; ++q) { while(k > 0 && P[q] != P[k]) k = next[k-1]; if (P[q] == P[k]) { k++; } next[q] = k; }}int kmp(const char T[],const char P[],int next[]){ int n,m; int i,q; n = strlen(T); m = strlen(P); makeNext(P,next); for (i = 0,q = 0; i < n; ++i) { while(q > 0 && P[q] != T[i]) q = next[q-1]; if (P[q] == T[i]) { q++; } if (q == m) { printf("Pattern occurs with shift:%d\n",(i-m+1)); } } }int main(){ int i; int next[20]={0}; //char T[] = "ababxbababcadfdsss"; char T[] = "ababxbab abcdabd abcadfdsss"; char P[] = "abcdabd"; printf("%s\n",T); printf("%s\n",P ); // makeNext(P,next); kmp(T,P,next); for (i = 0; i < strlen(P); ++i) { printf("%d ",next[i]); } printf("\n"); return 0;}
參考:
http://kb.cnblogs.com/page/176818/
http://www.cnblogs.com/10jschen/archive/2012/08/21/2648451.html