1. 程式人生 > >字符串(2)KMP算法

字符串(2)KMP算法

最大值 最長 pre 成了 -m hid code one 不可

給你兩個字符串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];//
這樣的目的是為了不遺漏重疊的匹配 12 } 13 } 14 }
kmp匹配

接下來我們思考如何求這個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算法