1. 程式人生 > 實用技巧 >芝士:KMP

芝士:KMP

kmp

引例

傳送門

過程

考慮普通的暴力的過程,

其複雜度主要是卡在模式串只向右邊移動一個位置

很明顯,這忽略了串內部的聯絡

就比如模式串\(aaabaaac\),如果在比較c的時候出現了錯誤,真的需要從頭開始比較麼?

很顯然,可以從\(b\)開始進行再一次的比較

那麼可以考慮求出以\(i\)為尾端點的字尾所能匹配到的最長字首

就比如上一個模式串中的\(nxt[7]=3\)(字串從1開始)

如果已經求出了\(nxt\)陣列,就可以利用上面所說的過程進行匹配

那麼現在的問題就在於怎麼求出\(nxt\)陣列

如果\(t[nxt[i-1]+1]==t[i]\),那麼就直接有\(nxt[i]=nxt[i-1]+1\)

如果沒有,也是考慮用匹配的思路去進行計算,即我可以考慮\(t[nxt[nxt[i-1]]+1]==t[i]\)

很明顯,用反證法可以說明這一定是最長的字首

就比如$abacabab $

很明顯有\(nxt[7]=3\)

但是發現\(t[4]!=t[8]\)

故用\(nxt[3]+1\)來匹配\(t[8]\)

因為字首和字尾是一樣的,所以演算法的正確性也不難證明

時間複雜度

求nxt

考慮總共的轉移過程,第一次一定是從\(nxt[i-1]\)開始

考慮如果要\(nxt[i]\)變大,那麼此時只會提供\(O(1)\),即只會在前一個+1

考慮\(nxt[i]\)變小,那麼一定是在\(nxt[i-1]\)

上進行操作

可以看作拔高和降低操作,一定是有高度才能降低

故總的時間複雜度為\(O(n)\)

匹配

考慮模式串在文字串中的已經匹配的區間為\([l,r]\)(文字串中的區間)

如果下一個字元可以進行匹配,那麼\(r\)就會往右移動,\(l\)不動

如果下一個字元不能進行匹配,那麼\(l\)就會往左移動,\(r\)不動

考慮每一次動都至少會動1個單位

所以時間複雜度為\(O(n)\)

程式碼

#include<iostream>
#include<cstring>
using namespace std;
char t[1000005];//文字串
char s[1000005];//模式串
int nxt[1000005];//失配陣列
int lens,lent;
int main()
{
    cin>>(t+1)>>(s+1);
    lent=strlen(t+1);
    lens=strlen(s+1);
    int j=0;
    for(int i=1;i<=lens;i++)
    {
        while(s[j+1]!=s[i]&&j)
            j=nxt[j];
        if(s[j+1]==s[i]&&i!=j+1)
            j++;
        nxt[i]=j;
    }   
    j=0;
    for(int i=1;i<=lent;i++)
    {
        while(t[i]!=s[j+1]&&j!=0)
            j=nxt[j];
        if(t[i]==s[j+1])
            j++;
        if(j==lens)
        {
            cout<<i-lens+1<<'\n';

    		j=nxt[j];
		}
	}
    for(int i=1;i<=lens;i++)
        cout<<nxt[i]<<' ';
    return 0;
}

exkmp

咕咕咕