題目來源:hihocode-KMP
思路:
KMP算法,在hihocoder也有講解,這裏說一下我的理解。
一般字符串匹配查找時,我們一般會使用這種方法
//以i開頭,開始和模式串進行匹配(s爲原串,p爲模式串)
for(int i=0,j=0;i<s.length();++i){
while(i<slen && j<plen && s[i] == p[j])i++,j++;
i = i - j;
if(j == p.length())return i;
}
這種算法的最壞時間複雜度是 原串的長度成以模式串的長度 即 s.length()*p.length() ;
但這種方法是可以改進的,這種方法在進行匹配的時候是有很多重複的匹配操作的;
例如有原串 babababcbab 模式串 bababb
在第一次匹配時,從s[0]開始,如圖,會在s[5]不匹配
然後進行第二次匹配,從s[1] 開始,如圖,會在s[5]不匹配
在這兩次中,第二次的匹配是從 s[1]開始,而s[1]顯然和p[0]不相等,這實際上這可以由第一次的匹配得到的;
可見這種方法的冗餘在某些情況下可能會更大。
故而有了KMP算法。
如果在匹配不成功後,我可以不將 i 改變,而是保持 i ,繼續和 p 某一位置 k 進行匹配,這樣算法不就成了線性的了嗎?
假設這個位置記爲next[j],也就是說,如果匹配不成功 i 不變,而 j 變爲 next[j];
那麼這個next[j]怎麼求呢?
實際上保持 i 不變 ,而使 i 繼續和 p 的某一位置 k 繼續匹配,
當且僅當 在 p.substring(0,k) == p.substring(k+1,j-1),如果有多個當然去k最大的那一個; (p.substring(i,j) 表示以i開始,j結尾的子串)
下面講具體的做法
next[j]表示當 s[i] != p[j] 時 j 應該向左移動到那裏;
顯然 next[0] = -1 表示 s[i] != p[0] 時,此時 i應加一,即 應該 向右移動一個距離
顯然 next[1] = 0;
然後從p[2]開始計算這時需要比較p[0]和p[1]
假設p[0]和p[1]相等,那麼 next[2]就應該爲1
再去求next[3]時, 由於p[1] == p[0],這時只用繼續比較p[2]和p[1]是否相等了
由上面的簡但陳述可以看出,這其實就是模式串和自身的匹配過程;
只需要建立兩個計數器i,j,i從1開始,j從0開始
如果 p[i] == p[j] 則 next[i+1] = j+1,i++,j++;
否則 j = next[j]
一直到遍歷到串尾
這是代碼:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 2100700
//截取原串的所有長度等於模式串p的所有子串;
int cmp0(string s,string p){
//以i開頭,長度爲p.length()
for(int i=0;i<s.length();++i)
if(s.substr(i,p.length()) == p)return i;
return -1;
}
int cmp0(char *s,char *p){
int slen=strlen(s),plen=strlen(p);
for(int i=0;i<slen;++i){
int j=i,k=0;
while(j<slen && k<plen && s[j] == p[k])j++,k++;
if(k == plen)return i;
}
return -1;
}
//使用string類進行查找
void Next(string p,int *next){
next[0]=-1,next[1]=0;
for(int i=1,j=0;i<p.length();){
if(j == -1 ||p[i] == p[j]){
++i,++j;
next[i]=j;
}else j=next[j];
}
}
int kmp(string s,string p){
int *next = new int[p.length()+1],cnt=0;
Next(p,next);
for(int i=0,j=0;i<s.length();){
while(j == -1 || i<s.length() && j<p.length() && s[i] == p[j])++i,++j;
if(j == p.length()){
cnt++;
j=next[j];
}
else j=next[j];
}
return cnt;
}
//使用字符數組查找
void Next(char *p,int *next,int plen){
next[0]=-1,next[1]=0;
for(int i=1,j=0;i<plen;){
if(j == -1 || p[i] == p[j]){
++i,++j;
next[i]=j;
}else j=next[j];
}
}
int kmp(char *s,char *p){
int cnt=0,slen,plen,*next;
slen=strlen(s),plen=strlen(p);
next = new int[plen+1];
Next(p,next,plen);
for(int i=0,j=0;i<slen;){
while(j == -1 || i<slen && j<plen && s[i] == p[j])++i,++j;
if(j == plen){
cnt++;
j=next[j]; //這一步很關鍵,不然會超時
}
else j=next[j];
}
return cnt;
}
char s[MAX],p[MAX];
int main(){
int n;
cin>>n;
while(n--){
cin>>p>>s;
cout<<kmp(s,p)<<endl;
}
return 0;
}