详解KMP入门

在网上很容易找到分析KMP算法的博客,但我觉得他们都没有讲到点子上去,导致一些读者越看越困惑,我则是看了刘汝佳的书后,自我比较通俗易懂的总结一下。


解决问题:给出字符串T和P,求P在T中第一次出现的位置,简称字符串匹配。



算法准备:最单纯的模拟算法就是每次枚举起始点,再判断是否匹配,效率O(nm),极低。

                  但根据经验,任何一个高效算法的提出就是减少的冗余状态,在模拟过程中,很明显做了一次又一次重复的事情,我们应该想办法避免重复。



分析:
T=‘ABRACADABRACABRACABRAE’

P=‘ABRAE’

首次匹配,发现T[4]='C',P[4]='E',导致匹配失败,简称失配,
然后根据模拟算法,接着我们又从T[1],P[0]开始重新匹配,但是明显我们没有用到第一次匹配时得到的信息,其实第一次匹配时,我们就知道T[1]和P[1]相等,T[0]和P[0]相等,如果我们能提前处理出P[1]与P[0]是否相等,我们就可以判断是接着匹配T[2]和P[1],还是重新开始下一个轮T[2]与P[0]的匹配。


所以,我们有了一个思路:

对于字符串P,提前预处理出f[i]表示当前P[i]与前面哪个j对应的P[j]相等

但很快我们发现,这在最坏情况下这并不能使效率提升多少。


再次深度分析

当我们一次匹配进行到P[j],T[i],而P[j]不等于T[i],导致失配,但我们已经已经知道P[j-1]等于T[i-1],我们可以在P中直接找与P[j-1]等效的地方P[k]重新开始匹配T[i-1],显然k<j,

若k>j,P[j]与T[i]并不相等,所以一定失配。

显然f数组中仍保存了大量的冗余信息

那么什么是等效? 等效的P[j]和P[k]即指P(1...k)=P(j-k+1....j)


因此我们可以预处理出f[j]表示与P[j]等效的P[k]的位置,那么失配后,


我们在j前面找到与P[j-1]等效的地方P[k],同时P[k+1]=T[i]。然后接着匹配P[k+2]与T[i+1],如果找不到对应的k,显然此次匹配是失败的,需要从头开始。


这就是KMP算法,很简单吧?但同时似乎效率并不高,因为有两层循环,且第二层在极端情况下好像是会退化的,真的是这样么?


可以这样计算时间复杂度:每一次匹配成功都会有j++,i++,而每一次j=f[j](即找到前一个与P[j-1]相等的地方)都会使j最少-1,但由于j最多只会增加n次,所以j=f[j]的次数最多也只有n次。所以效率为O(n)。


参考程序:

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=11000;
int f[maxn];
char P[maxn],T[maxn];
int n,m;
void getfail(char* P,int* f){
	f[0]=0;f[1]=0;
	for (int i=1;i<m;i++){
		int j=f[i];
		while (j && P[i]!=P[j])j=f[j];
		f[i+1]=P[i]==P[j]?j+1:0;
	}
}
int find(char* P,char* T,int* f){
	int j=0;
	for (int i=0;i<n;i++){
		while (j && T[i]!=P[j])j=f[j];
		if (P[j]==T[i])j++;
		if (j==m)return i-m+1;
	}
	return -1;
}
int main(){
	scanf("%d%d",&n,&m);
	scanf("%s%s",T,P);
	getfail(P,f);
	printf("%d\n",find(P,T,f));
	return 0;
}



  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值