終結字串匹配----------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 }
附上籤到題:
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 }
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 }