KMP:计算模式串出现的次数

参考资料:从头到尾彻底理解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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值