1. 程式人生 > >KMP算法 (字符串的匹配)

KMP算法 (字符串的匹配)

amp 第一個字符 jsb 提高 mar 理解 number 字符串的匹配 沒有

視頻參考

對於正常的字符串模式匹配,主串長度為m,子串為n,時間復雜度會到達O(m*n),而如果用KMP算法,復雜度將會減少線型時間O(m+n)。

設主串為ptr="ababaaababaa";,要比較的子串為a=“aab”;

KMP算法用到了next數組,然後利用next數組的值來提高匹配速度,我首先講一下next數組怎麽求,之後再講匹配方式。

next數組詳解

首先是理解KMP算法的第一個難關是next數組每個值的確定,這個問題困惱我很長時間,尤其是對照著代碼一行一行分析,很容易把自己繞進去。

定義一串字符串

ptr = "ababaaababaa";

next[i](i從1開始算)代表著,除去第i個數,在一個字符串裏面從第一個數到第(i-1)字符串前綴與後綴最長重復的個數。

什麽是前綴?

在“aba”中,前綴就是“ab”,除去最後一個字符的剩余字符串。

同理可以理解後綴。除去第一個字符的後面全部的字符串。

在“aba”中,前綴是“ab”,後綴是“ba”,那麽兩者最長的子串就是“a”;

在“ababa”中,前綴是“abab”,後綴是“baba”,二者最長重復子串是“aba”;

在“abcabcdabc”中,前綴是“abcabcdab”,後綴是“bcabcdabc”,二者最長重復的子串是“abc”;

這裏有一點要註意,前綴必須要從頭開始算,後綴要從最後一個數開始算,中間截一段相同字符串是不行的。

再回到next[i]的定義,對於字符串ptr = "ababaaababaa";

next[1] = -1,代表著除了第一個元素,之前前綴後綴最長的重復子串,這裏是空 ,即"",沒有,我們記為-1,代表空。(0代表1位相同,1代表兩位相同,依次累加)。

next[2] = -1,即“a”,沒有前綴與後綴,故最長重復的子串是空,值為-1;

next[3] = -1,即“ab”,前綴是“a”,後綴是“b”,最長重復的子串“”;

next[4] = 1,即"aba",前綴是“ab”,後綴是“ba”,最長重復的子串“a”;next數組裏面就是最長重復子串字符串的個數

next[5] = 2,即"abab",前綴是“aba”,後綴是“bab”,最長重復的子串“ab”;

next[6] = 3,即"ababa",前綴是“abab”,後綴是“baba”,最長重復的子串“aba”;

next[7] = 1,即"ababaa",前綴是“ababa”,後綴是“babaa”,最長重復的子串“a”;

next[8] = 1,即"ababaaa",前綴是“ababaa”,後綴是“babaaa”,最長重復的子串“a”;

next[9] = 2,即"ababaaab",前綴是“ababaaa”,後綴是“babaaab”,最長重復的子串“ab”;

next[10] = 3,即"ababaaaba",前綴是“ababaaab”,後綴是“babaaaba”,最長重復的子串“aba”;

next[11] = 4,即"ababaaabab",前綴是“ababaaaba”,後綴是“babaaabab”,最長重復的子串“abab”;

next[12] = 5,即"ababaaababa",前綴是“ababaaabab”,後綴是“babaaaababa”,最長重復的子串“ababa”;

還有另外一種方法,我看的有的書上寫著:

這裏我們定義next[1] = 0 , next[1] = 1;

再分析ptr字符串,ptr = "ababaaababaa";

跟上一個的情況類似,

next[1] = 0 ,事先定義好的

next[2] = 1 ,事先定義好的

next[3] = 1 ,最長重復的子串“”;1代表沒有重復,2代表有一個字符重復。

next[4] = 2 ,最長重復的子串“a”;追償的長度加1,即為2.

next[5] = 3 ,以下都跟之前的一樣,這種方法是最長的長度再加上一就可以了。

next[6] = 4

next[7] = 2

next[8] = 2

next[9] = 3

next[10] = 4

next[11] = 5

next[12] = 6

以上是next數組的詳細解釋。next數組求值 是比較麻煩的,剩下的匹配方式就很簡單了。

next數組用於子串身上,根據上面的原理,我們能夠推出子串a=“aab”的next數組的值分別為0,1,2.(按照我說的第二種方式算的)。

首先開始計算主串與子串的字符,設置主串用i來表示,子串用j來表示,如果ptr[i]與a[i]相等,那麽i與j就都加1:

技術分享圖片

prt[1]與a[1]相等,i++,j++:

用代碼實現就是

  1. if( j==0 || ptr[i]==a[j])
  2. {
  3. ++i;
  4. ++j;
  5. }

技術分享圖片

ptr[2]與a[2]不相等

此時ptr[2]!=a[2],那麽令j = next[j],此時j=2,那麽next[j] = next[2] = 1.那麽此時j就等於1.這一段判斷用代碼解釋的話就是:

  1. if( ptr[i]!=a[j])
  2. {
  3. j = next[j];
  4. }

加上上面的代碼進行組合:

在對兩個數組進行比對時,各自的i,j取值代碼:

  1. while( i<ptr.length && j< a.length)
  2. {
  3. if( j==0 || ptr[i]==a[i] )
  4. {
  5. ++i;
  6. ++j;
  1. next[i] = j;
  2. }
  3. else
  4. {
  5. j = next[j];
  6. }
  7. }



此時將a[j]置於j此時所處的位置,即a[1]放到j=2處,因為在j=2時出現不匹配的情況。

技術分享圖片

此時再次計算是否匹配,可以看出來a[1]!=ptr[2],那麽j = next[j],即此時j = next[1] = 0;

根據上面的代碼,當j=0時,執行++i;++j;

此時就變為:

技術分享圖片

此時ptr[3] = a[1],繼續向下走,下一個又不相等了,然後“aab”向後挪一位,這裏不再贅述了,主要的思想已經講明白了。到最後一直到i = 8,j=3時匹配成功,KMP算法結束。整個過程就結束了。

技術分享圖片
#include<bits/stdc++.h>

using namespace std;
const int maxn = 1000000+10;
int Next [maxn];
char str[maxn],mo[maxn];
void getNext()
{
    int i = 0,j = -1 , len = strlen(mo) ;
    while(i<len)
    {
        if(j==-1||mo[i] == mo[j]){Next[++i] = ++j;}
        else
            j = Next[j];
    }
}
int kmp()
{
    int i=0,j=0,l1=strlen(str),l2=strlen(mo);
    int ans = 0;
    while(i<l1)
    {
        if(j==-1 || mo[j] == str[i]) i++,j++;
        else j = Next[j];
        if(j == l2) ans++;
    }
    return ans ;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s%s",mo,str);
        Next[0] = -1 ;
        getNext();
        printf("%d\n",kmp());
    }
}
View Code

KMP算法 (字符串的匹配)