1. 程式人生 > >【KMP】【字符串】KMP字符串匹配算法 學習筆記

【KMP】【字符串】KMP字符串匹配算法 學習筆記

出現 調用 隨機 rri 形象 再看 aaaaa scan i+1

一、簡介

KMP是由Knuth、Morris和Prat發明的字符串匹配算法,它的時間復雜度是均攤\(O(n+m)\)。其實用Hash也可以做到線性,只不過Hash存在極其微小的難以避免的沖突。於是就有了KMP。

KMP算法用作模式串匹配,可以找到一個長為\(m\)的模式串在一個長為\(n\)的主串中出現的次數和位置。

二、樸素算法(\(O(nm)\))

實際上是枚舉模式串在主串中出現的位置,然後一一比對,出現錯誤就停止,移動到下一位。連續匹配成功\(m\)次就說明模式串在主串中出現了。

其實\(O(nm)\)是一個比較寬的上界,隨機數據遠沒有這麽慢。不過給出主串S="aaaaaaaaaaa"

,模式串T="aaab",就得每次匹配到最後才知道掛掉了。那麽我們既然已經知道這樣會掛掉,為什麽還要重復匹配呢?這就是KMP要解決的問題。

三、KMP算法

0.註意事項

KMP算法中,字符數組下標是從1開始而不是0,這樣會比較方便,在匹配的時候直接調用nxt數組就不用-1了,而且很直觀。

1.nxt數組(next)

nxt數組是KMP算法的核心。

nxt數組nxt[i]中存的是後綴字符與前綴字符相同的個數,且後綴字符不是前綴字符,也就是不能完全匹配自己。

對於ababcab而言,它的nxt數組是

nxt[1] nxt[2] nxt[3] nxt[4] nxt[5] nxt[6] nxt[7]
a ab aba abab ababc ababca ababcab
0 0 1 2 0 1 2
沒有後綴與前綴相同 沒有後綴與前綴相同 前綴"a"=後綴"a"

前綴"ab"=後綴"ab"

註意不能完全匹配自己

沒有後綴與前綴相同(失配直接=0) 前綴"a"=後綴"a" 前綴"ab"=後綴"ab"

看上去很簡單。但是它怎麽求呢?當然,如果遞推掌握的比較好,可以知道,如果T[i+1]==T[nxt[i]+1],那nxt[i+1]=nxt[i]+1。而失配了怎麽辦,是我們接下來要解決的問題。

2.nxt數組的作用

在上面我們提到了,KMP算法是減少了樸素算法的重復匹配,均攤下來每個字符只匹配了一次。在上面的表格中,我們看到了nxt數組處理出了模式串後綴與前綴匹配數。當我們用模式串匹配主串,在後綴失配時,在模式串裏與後綴相同

的就是我們預處理出的前綴了,因此我們只需要把模式串前綴挪到現在的位置來即可。這時我們會發現,中間可能跳過很多沒有用,也就是安排匹配不上的狀態,這就是KMP的優化。

3.nxt失配的求法

可以看出,求nxt數組,就是在用模式串匹配自己,且不匹配第一位。當我們發現T[i+1]與T[nxt[i]+1]失配時,比nxt[i]要次一點但是仍然與T[i]所在後綴相同的前綴就是nxt[nxt[i]]了。就是我們相當於退而求其次,“貪心地”按長度從大到小找一個最能匹配上後綴的前綴。

匹配不到的情況

比如說

ababababc
        ↑

如果匹配到第8+1位時失配了,這時就去找第nxt[8]+1位,(nxt[8]=6)也就是第6+1位。此時發現還是不行,繼續找第nxt[6]+1位……直到找到nxt[1]=0,退出循環,再看看第一位是否匹配,如果匹配,nxt[9]=1,否則nxt[9]=0。

重新匹配到的情況

abcaba
     ↑

這裏匹配到第5+1位發現失配,去找nxt[5]+1=2+1,仍然不行,但是nxt[2]=0了,此時退出循環,發現T[1]=T[6],所以nxt[6]=1。

其實情況還有很多很多種,這裏只是列舉了幾種,想讓nxt數組的求法變得更加形象一些。

四、KMP代碼

#include<cstdio>
#include<cstring>
char s[1000100];
char t[1000100];
int nxt[1000100];
int f[1000100];
int main()
{
    scanf("%s",s+1);
    scanf("%s",t+1);
    int n=strlen(s+1);
    int m=strlen(t+1);
    for(int i=2,j=0;i<=m;++i)
    {
        while(j&&t[i]!=t[j+1])
            j=nxt[j];
        if(t[j+1]==t[i])
            ++j;
        nxt[i]=j;
    }
    for(int i=1,j=0;i<=n;++i)
    {
        while(j&&(j==m||s[i]!=t[j+1]))
            j=nxt[j];
        if(t[j+1]==s[i])
            ++j;
        f[i]=j;
        if(f[i]==m)
            printf("%d\n",i-m+1);
    }
    for(int i=1;i<=m;++i)
        printf("%d ",nxt[i]);
    return 0;
}

【KMP】【字符串】KMP字符串匹配算法 學習筆記