字符串(2)KMP算法
給你兩個字符串a(len[a]=n),b(len[b]=m),問b是否是a的子串,並且統計b在a中的出現次數,如果我們枚舉a從什麽位置與匹配,並且驗證是否匹配,那麽時間復雜度O(nm),
而n和m的範圍為10^5,這樣做顯然超時,因此我們就要用到神奇的KMP算法,在O(n)的時間內解決這一類的問題。
首先給出兩個字符串
A:abababaababacb
B:ababacb
首先我們思考樸素算法,我們枚舉A串的每一位作為開始與B串匹配的位置,然後一位一位進行檢驗,如果匹配失敗則會從B串的開始重新匹配,這也是樸素算法為什麽會超時的原因所在,那麽我們可不可以不從B串的開始與A串的下一位匹配,而是直接找到一個可以與A串枚舉到的那一位匹配的B串的一個位置,這樣我們只需要掃描一遍字符串A,然後在更新可以匹配到B串的哪一個位置。
舉個例子:
對於字符串A,B,他們的前五位是一樣的,可以匹配。
但是第6位就無法匹配了,所以我們需要調整B串的位置重新匹配,通過觀察我們發現B串的前3位和A串的3到5位可以匹配,所以我們可以直接將j的值改為3,然後繼續和A串匹配。
這樣一來我們就又可以繼續向後匹配了。
但是i=8時又無法匹配了,我們需要繼續調整B串的位置以便於進行匹配。
然而這樣仍然不能匹配,那就繼續移動,直至可以匹配為止。
最終我們成功將A串與B串匹配成功。
我們根據B串的移動規律可以構造出這樣的一個p數組,使得p[j]表示B[1…j]=B[j-k+1…j]的k的最大值(即B串前j個字符最長相同的前綴和後綴的長度),這樣我們就可以直接將B串進行上述的移動操作了。
1 void kmp() 2 { 3 int j=0; 4 for(int i=0;i<n;i++) 5 { 6 while(j>0&&b[j+1]!=a[i+1]) j=p[j];//如果下一位不能匹配就移動B串 7 if(b[j+1]==a[i+1]) j++;//如果可以匹配就繼續匹配下一位 8 if(j==m)//這裏表示完全匹配,也就是A串和B串成功匹配了 9 { 10 printf("%d\n",i-m+2); 11 j=p[j];//kmp匹配這樣的目的是為了不遺漏重疊的匹配 12 } 13 } 14 }
接下來我們思考如何求這個p數組,假如我們知道了p[1…j-1]的值,那我們如何求p[j]的值呢?如果B[i+1]==B[j+1],那麽顯然p[j]=p[j-1]+1,因為這相當於前綴後綴都加了一個相同的字符,總長都加上1,那麽若果B[i+1]!=B[j+1]我們可以考慮將j向後退一步,也就是減小j的值,再進行匹配。
1 void pre() 2 { 3 p[1]=0; 4 int j=0; 5 for(int i=1;i<m;i++) 6 { 7 while(j>0&&b[i+1]!=b[j+1]) j=p[j]; 8 if(b[i+1]==b[j+1]) j++; 9 p[i+1]=j; 10 } 11 }求p數組
這樣我們就成功完成了kmp算法
字符串(2)KMP算法