题目来源: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;
}