1. 程式人生 > >KMP演算法詳細講解

KMP演算法詳細講解

文章篇幅有點長,但是最後一張圖真的很棒,希望讀者堅持慢慢看到最後,必有收穫。

字串單模式問題描述:

給定文字串text,和模式串pattern,在文字串text中找到模式串pattern第一次出現的位置。

一、最基本的字串單模式匹配演算法:暴力求解(Brute Force):時間複雜度O(m*n)

設文字串text = "ababcabcacbab",模式串為patten = "abcac"     其匹配過程如下圖所示。

黑色線條代表匹配位置,紅色斜槓代表失配位置。





可以看到每次失配之後,模式串都向右移動一位在去從第一個字元開始於文字串匹配。並且

在已經知道很多字元都配不上的情況下,還要這樣移動著去配,是非常浪費時間的。

BF演算法:

  1. int Brute_ForceSearch(string t,string p)  
  2. {  
  3.     int i=0,j=0;  
  4.     int len = t.length(),len_p = p.length();  
  5.     while((i <len)&&(j<len_p)){  
  6.         if(t[i+j]==p[j])///若匹配則模式串後移  
  7.             j++;  
  8.         else{///不匹配,則比較下一個位置,模式串回到0位  
  9.             i++;  
  10.             j = 0;  
  11.         }  
  12.     }   
  13.     if(j >= len_p)  
  14.         return i;  
  15.     return -1;  
  16. }  
二、KMP演算法的本質。

在BF中,假如從文字串的第i個字元來開始於模式串匹配。當匹配到模式串的第j位發現失配

即text[i+j] != patten[j]的時候,我們又從文字串的第i+1個位置來重新開始匹配。儘管我們已經

知道了好多字元其實根本就匹配不上,我們還是進行了這個過程,這個時候回溯的過程會非常

耗費我們的時間。而KMP演算法的實質就是,當遇到text[i+j] != patten[j]的時候,但是我們知道

模式串中的 0~j-1 位置上的字元已經於i ~ i+j-1位置上的字元是完全匹配的。這樣我們可以在0~j-1

中找到一個字首A和字尾B相等並且最長的那個串,然後將A移動到B的位置再開始重新匹配即可。

這樣就減少了一些不必要的匹配。時間複雜度O(n)

Next陣列的求法:

普及兩個概念:

字首和字尾:例如一個字串:abcd

它的字首有                         它的字尾有

a                                                      d

ab                                                  cd

abc                                               bcd

我們這裡所說的字首、字尾不包括字串自身。

求next陣列的時候,對於模式串的位置j,考察patten[j-1].查詢字串patten[j-1]的最大相等的字首和字尾。

假設最大相等字首和字尾長度為k,則有k使得  p[0]p[1]p[2]......p[k-2]p[k-1] = p[j-k]p[j-k+1]......p[j-2]p[j-1]。

例如模式串Patten = "abaabcac"。其next陣列如圖所示:


我們可以看圖中第一個c字元的下標是5,其next陣列的值是2.也就是說,模式串裡面當配到c這個

字元失配的時候,再文字串中,abaab都是已經配好的,我們發現patten前面出現過ab,所以我們從

之前的ab串的後一個字元繼續匹配就行了。如下圖所示:


從圖中我們可以看出藍色位置位置失配,藍色位置前面的字串中,最長公共字首字尾是ab,則我們可以直接

把模式串patten向右滑,讓黃色位置格子於文字串失配的地方對其。則藍色格子失配即模式串第6個字元失配,

就從模式串的第三個字元開始配,第三個字元的下標為2,則next[5] = 2;字串滑過去後如下圖所示。


這樣我們一下滑過去,就跳過了文字串中的text[1],text[2]。避免了這些不必要的匹配。

現在我們開始來講求next陣列。巨集觀上我們按下圖的步驟求next。

我們用index來表示next陣列的下標:


當index = 2時,求ab的最大相等字首串,字尾串。


當index = 3時,求aba的最大相等字首串,字尾串。


當index = 4時,對abaa求最大相等字首串、字尾串。


當j=5時,求abaab的最大相等字首串,字尾串。


。。。。。。。。。。。。。。。。。。不在往後求了,就是按照這樣的方法一直求下去。

看一下程式碼實現:

  1. /*  
  2. 付完整程式碼  
  3. */  
  4. #include <iostream>
  5. #include <string.h>
  6. #include <stdio.h>
  7. using namespace std;  
  8. const int maxn = 1000010;  
  9. char text[maxn];  
  10. char patten[maxn];  
  11. int next[maxn];  
  12. void GetNext()  
  13. {  
  14.     int len_p = strlen(patten);  
  15.     next[0] = -1;  
  16.     int k = -1,j = 0 ;  
  17.     while(j<len_p)  
  18.     {  
  19.         if(k == -1||patten[j] == patten[k])  
  20.         {  
  21.             ++k;++j;  
  22.             next[j] = k;  
  23.         }  
  24.         else  
  25.             k = next[k];  
  26.     }  
  27. }  
  28. int KMP()  
  29. {  
  30.     int ans=-1,i = 0,j = 0;  
  31.     int len_p = strlen(patten),n=strlen(text);  
  32.     while(i<n)  
  33.     {  
  34.         if(j==-1||text[i] == patten[j])  
  35.         {  
  36.             ++i;++j;  
  37.         }  
  38.         else  
  39.             j = next[j];  
  40.         if(j == len_p){  
  41.             return i-len_p;  
  42.             break;  
  43.         }  
  44.     }  
  45.     return ans;  
  46. }  
  47. int main()  
  48. {  
  49.     int t;  
  50.     scanf("%d",&t);  
  51.     while(t--)  
  52.     {  
  53.         scanf("%s%s",patten,text);  
  54.         GetNext();  
  55.         for(int i = 0; i <strlen(patten); i++)  
  56.             printf("%d ",next[i]);  
  57.         printf("\n");  
  58.         printf("%d\n",KMP());  
  59.     }  
  60.     return 0;  
  61. }  
程式碼測試:

當然大家都直到程式碼該怎麼寫,也知道上面找前後綴的過程,但是肯定看程式碼的時候還是會覺得一臉懵。

想當初我也是這樣的,理解前後綴,但是就是看不懂程式碼,所以為了幫助理解程式碼我又搞了下面這個圖。

這時我這篇部落格裡面最棒的一個圖片了,個人認為: