KMP字串匹配演算法詳解
KMP演算法利用匹配失敗後的資訊,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是實現一個next()函式,函式本身包含了模式串的區域性匹配資訊。時間複雜度O(m+n)。
Next()函式的詳解
- 把將要進行next計算的字串S分成 k ,j 前後兩串,k代表前串開頭所在的序號,j代表後串開頭所在的序號,起始的時候j=1,k=0。
- 我們比較一下前串 後串是否相等,要怎麼比較呢,肯定是比較S[j]==S[k],如果相等,那麼next[j+1]=k+1,然後j++,k++。關鍵就是理解這個next[j+1]=k+1(為什麼k+1?):簡單說就是S串中的第j+1個字元的next函式值由他前面的字元與前串相等的個數來決定,就是說串中的第j+1個字元的next函式值,是由他前面的字串決定的。
- 當S[j]!=S[k],即不相等的時侯,那麼j不動,k返回到開頭(因該是next[k]位置,便於理解先假設是返回k=0處),即從頭比較S[0]與S[j],S[1]與S[j+1]。
例如:第 j+1 個字元的next函式值next[j+1]等於3,意味著它的前三個字串,S[j-2]S[j-1]S[j] =S[0]S[1]S[2]。
例一:模式串:abcaabcba
下標 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
模式串 |
a |
b |
c |
a |
a |
b |
c |
b |
a |
next值 |
-1 |
0 |
0 |
0 |
1 |
1 |
2 |
3 |
0 |
1.第一個字元的next值令為-1。令第二個字元b的next值為0。初始k=0,j=1。開始比較S[k] 和S[j]。
2.比較S[0] !=S[1],所以j++,k不變,next[j=2]=k=0。
3.比較S[0] !=S[2],所以j++,k不變,next[j=3]=k=0。
4.比較S[0]==S[3],所以j++,k++,next[j=4]=k=1。
5.k=1了,所以比較S[1] !=S[4],k返回到next[k]位置,即k=next[1]=0,然後比較S[k=0] == S[4],所以 j++,k++,next[j=5]=k=1。
6.比較S[1]==S[5],所以j++,k++,next[6]=k=2。
7.比較S[2]==S[6],所以j++,k++,next[7]=k=3。
8.比較S[3] !=S[7],所以k返回到next[k=3]位置,即k=next[3]=0,然後比較S[k=0] !=S[7],所以j++,k=0不變,next[8]=k=0。
在例一中,每次不相等時返回的都是k=next[k]=0,都是返回到了開頭,下面一個不是返回到開頭0的情況:
例二:模式串:aabcaaabaac
下標 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
模式串 |
a |
a |
b |
c |
a |
a |
a |
b |
a |
a |
c |
next值 |
-1 |
0 |
1 |
0 |
0 |
1 |
2 |
2 |
3 |
1 |
2 |
從 j=5,k=1的時候開始
5.比較S[1]==S[5],所以j++,k++,next[j=6]=k=2。
6.比較S[2] !=S[6],所以k返回到next[k=2]位置,即k=next[2]=1,然後比較S[k=1]==S[6],所以 j++,k++,next[7]=k=2。
……
因此,發現K的退回是退回到next[k]的位置,即S[j]!=S[k]時,k=next[k]。
KMP的演算法思想
和BF演算法相比,KMP演算法主要是在模式串上下功夫,通過先求得模式串對應的next[ ]陣列,當兩個字串中字元匹配失敗時候將模式串的下標回溯到next[ ]中儲存的下標位置,而BF演算法是直接回溯到模式串的0下標,即開始第一個字元。所以KMP演算法的時間複雜度要比BF演算法好。
KMP演算法程式碼
1 #include<stdio.h> 2 #include<string.h> 3 4 char* s = "aabcaaabaac"; 5 char* t = "aac"; 6 7 int next[100]; //定義next陣列 8 9 void getNext(char *s, int next[]) 10 { 11 int k=-1; / /k代表前串起始位置 12 int j=0; //後串起始位置,一直增加 13 next[0] = -1; //令第一個字元的next值為-1 14 15 while(j < strlen(s) - 1) //當後串小於最大下標-1 16 { 17 if(k == -1 || s[j] == s[k]) //匹配的情況下,即s[j]==s[k],next[j+1]=k+1; 18 { 19 ++j; 20 ++k; 21 next[j] = k; 22 } 23 else //若不匹配,即p[j]!=p[k],k=next[k] 24 k = next[k]; 25 } 26 } 27 28 int KMP(char* s, char* t) 29 { 30 int i = 0; //i從s串開始 31 int j = 0; //j從t串開始 32 int sLength = strlen(s); //s串的長度 33 int tLength = strlen(t); //t串的長度 34 while((i < sLength) && (j < tLength)) //當下標i和j都不越界時 35 { 36 if(j == -1 || s[i] == t[j]) //當模式串t中第一個字元與目標串s中某個字元匹配失敗時,i應該移動到目標串s的下一個目標,再和模式串t的第一個字元進行比較,或者s的第i個字元和t的第j個字元相等,則將i++和j++ 37 { 38 i++; 39 j++; 40 } 41 else 42 { 43 //i=i-j+1;j=0; //這是普通的BF演算法,將模式串的下標從0開始 44 j = next[j]; //KMP演算法是將模式串的j下標從next[j]開始 45 } 46 } 47 if(j >= tLength) 48 return i - tLength; 49 else 50 return 0; 51 } 52 53 int main() 54 { 55 getNext(s, next); 56 printf("%d", 1 + KMP(s, t)); 57 return 0; 58 }