KMP

2021 年就学了KMP,2023写一篇详细点的总结。

首先我们需要理解朴素做法

  • 枚举开始匹配的位置 \(i\),和匹配串中的每个位置逐一匹配,失败就停止移动继续匹配,最坏情况复杂度高达 \(O(mn)\)

上述做法的缺陷就在于没有充分利用信息,比如匹配失败时就从头开始。我们考虑一次匹配中,如果失败了,那么至少需要移动多少,首先贴一张图。
img
如上图所示,蓝色的线段表示文本串,红色的串表示模式串,竖着的绿线表示在某次匹配中,竖线及其之前的所有位置均已匹配成功,红圈和绿圈的两个位置为第一次匹配的失败位置。那匹配之后至少要移动多少呢,假设是最下面红色线段的位置,那么画橙圈的部分必然的部分必然全等,而因为两个红色的是平移构造而成的,所以对应位置全等,所以本质上就是前缀和后缀相等。而为了求出最少移动距离就可以转换为最大前后缀长度。接下来就是求这个最长公共前后缀的问题了。这个求解方式其实和上述的匹配过程基本一致,还是以图为例。用 \(ne_i\) 表示匹配到 \(i\) 的结果。
img
上图中我们已经求好了位置 \(i\) 之前的所有答案,现在想要推出后面的,由于前面的位置相等,其实还是一样的方法就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=1000001;
char s1[N],s2[N];
int ne[N],n,m;
int main(){
	scanf("%s%s",s1+1,s2+1);
	n=strlen(s1+1),m=strlen(s2+1);
	for(int i=2,j=0;i<=m;++i){
		while(j&&s2[j+1]!=s2[i])j=ne[j];
		if(s2[j+1]==s2[i])++j;
		ne[i]=j;
	}
	for(int i=1,j=0;i<=n;++i){
		while(j&&s2[j+1]!=s1[i])j=ne[j];
		if(s2[j+1]==s1[i])++j;
//		cout<<j<<endl;
		if(j==m){
			printf("%d\n",i-m+1);
			j=ne[j];
		}
	}
	for(int i=1;i<=m;++i)printf("%d ",ne[i]);
	return 0;
}
posted @ 2023-04-06 15:51  wscqwq  阅读(7)  评论(0编辑  收藏  举报