問題:已知兩個字符串分別爲S、P如下圖,判斷s串中存在幾個p串,並輸出其起始位置?
目錄
兩種算法的區別:
黃線爲已經匹配的部分,兩段綠線爲c之前相同的前後綴,當c失配時,分別從箭頭位置開始匹配的效果是完全相同的。
暴力是從第一個箭頭開始,KMP是從第二個開始,這就是兩中方法的差別。(不知道能不能看懂)
一、暴力解決:
複雜度:O(N*M)
1、abab匹配成功,但當 i=4 (j=4) 時d與c失配。
2、令j=0,i=1重新開始匹配,當i=1(j=0)時失配,繼續移動。
3、重複上面的步驟,知道最後匹配成功,輸出起始位置。
代碼模板:
void Search(string a,string p)
{
int la=s.size(),lp=p.size();
for(int i=0;i<la;i++){
for(int j=0;j<lp;j++){
if(a[i]!=p[j]){
break;
}
if(j==lp-1){
cout<<i<<endl;
}
}
}
}
上面的複雜度確實很高,KMP算法的優點就是用線形的時間複雜度解決這個問題。
二、KMP算法
複雜度:O(N+M)
1、同樣abab匹配成功,但當 i=4 (j=4) 時d與c失配。
2、此時令j=Next[j],即j=Next[4]=2,i不變,繼續比較,此時d與a失配。(先不要關心Next[4]=2什麼意思,求Next數組傳送門)
3、重複上面的步驟,直到最後完全匹配爲止。
比較上面的兩種方法應該能看出複雜度的曲別。
Next數組含義:
以p串爲例子,它的每個座標對應的Next數組的值如下:
Next數組的含義:Next[4]=2的含義就是 [0,3] 這個子串的前綴與後綴最大相同的字符數目爲2。
求解Next數組:
先上代碼(找了很多天,這個算是最好用的了) 感謝:Venishel
///p[k]爲前綴,p[i]爲後綴
void getNext(string p,LL lp,LL Next[]){
LL k=0;
for(LL i=1;i<lp;i++){
while(k&&p[i]!=p[k]) k=Next[k];
if(p[i]==p[k]) k++;
Next[i+1]=k;
}
}
KMP算法匹配:
感謝: v_JULY_v
該字符對應的Next 值會告訴你下一步匹配中,j應該跳到哪個位置(跳到Next [j] 的位置)。如果Next [j] 等於0,則跳到p串的開頭,若Next [j] 不爲0,代表下次匹配跳到Next[j]的位置,而不是跳到開頭,且具體跳過了j-Next[j] 個字符。
像下面的這種情況:
1、現在d與c失配:
2、Next[j]=2,即j跳到2的位置。
代碼:
void kmp(string a,string p,LL la,LL lp){
LL j=0;
for(LL i=0;i<la;i++){
while(j&&a[i]!=p[j]) j=Next[j];
if(a[i]==p[j]) j++;
if(j==lp) cnt++,j=0;
}
}
KMP完整代碼:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAX=1e6;
LL Next[MAX+5],cnt;
void getNext(string p,LL lp,LL Next[]){
LL k=0;
for(LL i=1;i<lp;i++){
while(k&&p[i]!=p[k]) k=Next[k];
if(p[i]==p[k]) k++;
Next[i+1]=k;
}
}
void kmp(string a,string p,LL la,LL lp){
LL j=0;
for(LL i=0;i<la;i++){
while(j&&a[i]!=p[j]) j=Next[j];
if(a[i]==p[j]) j++;
if(j==lp) cnt++,j=0;
}
}
int main()
{
string a,p;
cin>>a>>p;
LL la=a.size(),lp=p.size();
getNext(p,lp,Next);
kmp(a,p,la,lp);
cout<<cnt<<endl;
return 0;
}