芝士:KMP
阿新 • • 發佈:2020-12-09
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
咕咕咕