1. 程式人生 > 實用技巧 >終結字串匹配----------KMP演算法

終結字串匹配----------KMP演算法

對於KMP很早之前就學過,但是一直沒寫部落格,當再次用到時,發現忘的差不多了,所以題主花了兩個小時重新拾取了一下,現在打算留下筆記,寫下自己對KMP演算法的理解。(在看本文章之前需要對KMP大致的理解,本文章只針對Next陣列的求法+程式碼解析)

第一個問題:

  字串1:  abacabababacababc

  字串2:  abacababc

   

    在字串1中查詢字串2出現的次數,常規思路,暴力,時間複雜度:O(n2),只能解決1000長度以內的字串。

暴力演算法因為存在指標回溯,導致複雜度偏高。暴力基礎上解決指標回溯就是KMP演算法,時間複雜度:O(n)

學習KMP演算法前,需要先學習下什麼是最長公共前後綴。

假設字串長度為len, 即str【0】 ~ str【len-1】 , 設定 K在區間 【0,len-1】內的任意整數

字串字首:包含首字元在內的子串。 即 子串: str【0】 ~ str【k】

字串字尾:包含尾字元在內的子串。   即 子串: str【k】 ~ str【len-1】

公共前後綴:即字串字首完全等於字串字尾。

最長公共前後綴: 公共前後綴加個最長條件即可。

那麼來求字串abacababc 的next。

第一步分離出全部的字串字首。

第二步:將得到最長公共前後綴長度表向下移一位,末尾捨去,首位填-1,得到的陣列就是next陣列。(如下圖)

那麼得到了next的陣列有什麼用呢?我們模擬一遍匹配就好了

還是上面的例子:

  字串1:  abacabababacababc

  字串2:  abacababc

我們得到的字串2的next陣列為 {-1,0,0,1,0,1,2,3,2};

首先暴力匹配, 設定變數i是指向字串1待匹配的元素, 變數k指向字串2待匹配元素

回退如下

不難看出:

    當不成功匹配的時,則k回退,k值等於對應的next的值。

  即:next陣列的作用: 當發現不匹配時,k回退到對應的next值,即next值之前的元素都已經記憶匹配,如上圖綠色下劃線部分。

程式碼解析

next:

 1 void GetNext(){   //next陣列求法 
2 int i=0,k=-1; 3 Next[0]=-1; 4 while (i<m){ 5 if (k==-1||st[i]==st[k]){ 6 i++; 7 k++; 8 Next[i]=k; 9 } 10 else 11 k=Next[k]; 12 } 13 }

唯一不好理解的應該是 第11行的 k=Next【k】,為什麼要這樣回退?

如下例子。

KMP:

 1 int KMP(char str[],char st[]){ //不存在輸出-1,存在則輸出首地址 
 2     GetNext();
 3     int i=0,j=0,n=strlen(str),m=strlen(st);
 4     while (i<n&&j<m){
 5         if (j==-1||str[i]==st[j]){
 6             i++;
 7             j++;
 8             
 9         }
10         else
11             j=Next[j];
12     }
13     if (j>=m)    return i-j+1;
14     return -1;
15 }

附上籤到題:

hdu1711

 1 #include<iostream>
 2 using namespace std;
 3 
 4 
 5 int n,m,str[1000010],st[1010],Next[10010];
 6 
 7 void GetNext(){
 8     int i=0,k=-1;
 9     Next[0]=-1;
10     while (i<m){
11         if (k==-1||st[i]==st[k]){
12             i++;
13             k++;
14             Next[i]=k;
15         }
16         else
17             k=Next[k];
18     }
19 }
20 
21 int KMP(){  
22     GetNext();
23     int i=0,j=0;
24     while (i<n&&j<m){
25         if (j==-1||str[i]==st[j]){
26             i++;
27             j++;
28             
29         }
30         else
31             j=Next[j];
32     }
33     if (j>=m)    return i-j+1;
34     return -1;
35 }
36 int main(){
37     int T;
38     scanf("%d",&T);
39     while (T--){
40         scanf("%d%d",&n,&m);
41         for (int i=0;i<n;i++)
42             scanf("%d",&str[i]);
43         for (int i=0;i<m;i++)
44             scanf("%d",&st[i]);
45         cout<<KMP()<<endl;
46     }
47     return 0;
48 }

hdu2087

 1 #include<iostream>
 2 #include<cstring>
 3 using namespace std;
 4 
 5 char str[1010],st[1010];
 6 int n,m,ans,Next[1010];
 7 void GetNext(){
 8     int i=0,k=-1;
 9     Next[0]=-1;
10     while (i<m){
11         if (k==-1||st[i]==st[k]){
12             i++;
13             k++;
14             Next[i]=k;
15         }
16         else k=Next[k];
17     }
18 }
19 
20 void KMP(){
21     GetNext();
22     ans=0;
23     int i=0,j=0;
24     while (i<n){
25         if (j==-1||str[i]==st[j]){
26             i++;
27             j++;
28         }
29         else
30             j=Next[j];
31         if (j==m){
32             ans++;
33             j=0;
34         }
35     }
36     return ;
37 }
38 int main(){
39     while (true){
40         scanf("%s",str);
41         if (str[0]=='#')    break;
42         scanf("%s",st);
43         n=strlen(str);
44         m=strlen(st);
45         KMP();
46         printf("%d\n",ans);
47     }
48 
49     return 0;
50 }