参考资料:从头到尾彻底理解KMP
题目来源:#1015 : KMP算法
实现代码:#182538:solution
一、问题描述:
判断一段文字(原串,str_src,长度为len_src)里面是不是存在那么一些特殊的文字(模式串,str_ptn,长度为len_ptn)?如若存在,计算该模式串出现的次数。
输入:第一行一个整数N,表示测试数据组数。接下来的N*2行,每两行表示一个测试数据。在每一个测试数据中,第一行为模式串,由不超过10^4个大写字母组成,第二行为原串,由不超过10^6个大写字母组成。其中N<=20。
输出:对于每一个测试数据,按照它们在输入中出现的顺序输出一行Ans,表示模式串在原串中出现的次数。
样例输入:
5
HA
HAHAHA
WQN
WQN
ADA
ADADADA
BABABB
BABABABABABABABABB
DAD
ADDAADAADDAAADAAD
样例输出:
3
1
3
1
0
二、问题分析:
经典模式匹配问题。
基础方法,穷举法:从原串的各个位置开始与模式串进行比较,如若与模式串匹配则存在模式串,计数加1。但此时算法复杂度为O(len_src*len_ptn)。
改进方法1,KMP:首先利用KMP算法,得到模式串的next数组,然后利用next进行匹配,如若匹配成功,则返回原串中起始匹配位置pos,计数加1,之后从原串pos+1处再次利用next进行匹配,直到匹配失败。因为要计算模式串出现的次数,故此时算法的最坏复杂度仍为O(len_src*len_ptn)。
改进方法2,KMP:对改进方法1,时间复杂度仍然很高,究其原因是未充分利用next数组。每次匹配成功(原串匹配起始位置为pos)后下一次判断匹配起始位置(为pos+1)取用有问题,应该如同简单KMP方法一样,不要回溯原串索引位置,而是利用next数组,调整模式串的索引位置。设匹配成功时,原串索引起始位置为pos,则下次匹配判断的原串起始索引位置应为pos+len_ptn,而模式串的起始索引位置应为next[len_ptn]。
三、算法实现:
改进方法1,KMP:
<pre name="code" class="cpp">#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
void get_Next(const string &str_ptn,int *next);
int KMP(const string &str_ptn,const string &str_src,int pos,const int *next);
int my_KMP(const string &str_ptn,const string &str_src);
int main(int argc,char *argv[]){
string str_src,str_ptn;
int t;
cin>>t;
while(t--){
cin>>str_ptn>>str_src;
cout<<my_KMP(str_ptn,str_src)<<endl;
}
}
void get_Next(const string &str_ptn,int *next){
next[0]=-1;
int i=0,k=-1;
int len_ptn=str_ptn.size();
while(i<len_ptn-1){
if(k==-1||str_ptn[k]==str_ptn[i]){
++k;
++i;
if(str_ptn[i]!=str_ptn[k]){
next[i]=k;
}else{
next[i]=next[k];
}
}else{
k=next[k];
}
}
}
int KMP(const string &str_ptn,const string &str_src,int pos,const int *next){
int i=pos,k=0;
int len_src=str_src.size();
int len_ptn=str_ptn.size();
while(i<len_src&&k<len_ptn){
if(k==-1||str_src[i]==str_ptn[k]){
++i;
++k;
}else{
k=next[k];
}
}
if(k==len_ptn){
return i-k;
}else{
return -1;
}
}
int my_KMP(const string &str_ptn,const string &str_src){
if(str_ptn.size()==0){
return 1;
}
int *next=new int[str_ptn.size()];
if(next==NULL){
exit(0);
}
get_Next(str_ptn,next);
int pos=-1,count=0;
while((pos=KMP(str_ptn,str_src,pos+1,next))!=-1){
++count;
}
delete[] next;
return count;
}
改进方法2,KMP:
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
void get_Next(const string &str_ptn,int *next);
bool KMP(const string &str_ptn,const string &str_src,int &i,int &k,const int *next);
int my_KMP(const string &str_ptn,const string &str_src,int *next);
int main(int argc,char *argv[]){
int next[10001];
string str_src,str_ptn;
int t;
cin>>t;
while(t--){
cin>>str_ptn>>str_src;
cout<<my_KMP(str_ptn,str_src,next)<<endl;
}
}
void get_Next(const string &str_ptn,int *next){
next[0]=-1;
int i=0,k=-1;
int len_ptn=str_ptn.size();
while(i<len_ptn-1){
if(k==-1||str_ptn[k]==str_ptn[i]){
++k;
++i;
if(str_ptn[i]!=str_ptn[k]){
next[i]=k;
}else{
next[i]=next[k];
}
}else{
k=next[k];
}
}
}
bool KMP(const string &str_ptn,const string &str_src,int &i,int &k,const int *next){
int len_src=str_src.size();
int len_ptn=str_ptn.size();
while(i<len_src&&k<len_ptn){
if(k==-1||str_src[i]==str_ptn[k]){
++i;
++k;
}else{
k=next[k];
}
}
if(k==len_ptn){
k=next[k];
return true;
}else{
return false;
}
}
int my_KMP(const string &str_ptn,const string &str_src,int *next){
int i=0,k=0,count=0;
if(str_ptn.size()==0){
return 1;
}
get_Next(str_ptn+'$',next);
while(KMP(str_ptn,str_src,i,k,next)){
++count;
}
return count;
}