KMP算法

模板】KMP字符串匹配

从暴力说起

KMP字符串匹配用于两个字符串中,是否一个字符串是另一个的子串

我们称其中两个串一个为S(长度为n),一个为P(长度为m),问是否P为S的字串

这种题暴力的想法很容易想到但时间复杂度为\(O(nm)\)

遇到KMP

其实KMP也就只是在暴力的想法上进行了优化,不过优化的程度很高

举例
S:a b a d a b a
P: a b b

暴力思路就是枚举主串的每个字符,然后从枚举到的字符后m个字符是否有一样即可

而暴力的思路明显存在缺点,一旦匹配失败就只右移一位,然后从头继续匹配

而KMP的思路是移动多位(那么此时同学会想,移动多位会不会丢失一些可能可以匹配的字符串?且接着往下看)

这个疑问非常有道理,所以KMP的算法妙就妙在这里,跳过一些不可能匹配成功的位置

next数组

这个问题可以被很好的解决,通过next数组

next[i]数组存储的是以P[0~i]为字符串的以长度为k的前缀和后缀相等,k取最大(k<=i)(长度不能等于i+1,因为如此自己便等于自己,自己等于自己无意义)

简而言之,最长的相同前缀后缀的长度

假如匹配到某个位置\(x(x<m)\)

\(S[l-r]=P[0-x]\)

但是\(S[r+1]!=P[x+1]\)

按暴力的算法我们会想去从头开始匹配
例如
\(S[l+1]==P[0]\)

但我们发现其实\(S[l-r]=P[0-x]\)这一段是匹配过的

如果存在\(S[l-r]\)中某一段\([l'-r']\)等于某一段\([h\) ~ \(t]\),就可以使$ P[0 $~ \(r'-l']=S[h\) ~ \(t]\)
同时从其他地方开始匹配也绝无可能,这样就能大大降低复杂度

你会发现前面引入的next数组完美解决了上述问题,

最长同长度前后缀满足某段等于某段,而且最长的前后缀,使得其他地方也不存在匹配成功的可能(很好的思考题,这里的原因)

证明

  • 证明(非严谨)
    如果存在 $P[0 $ ~ $ x]=S[r-x+1$ ~\(r]\)且不是最长同前缀后缀,那么x一定小于最长同前缀后缀的长度
next数组求法

也有暴力求法,但时间复杂度为\(O(m^2)\),违背了我们降低算法复杂度的初心

你想想\(next[i]\)\(next[i-1]\)有没有联系

如果\(P[next[i-1]]=P[i]\),显然\(next[i]=next[i-1]+1\)

如果≠

  • 因为存在$P[0 $ ~ $next[i-1]]=p[i-1-next[i-1] $ ~ \(i-1]\)
    所以存在\(P[0\)~\(next[ next[ i-1 ] ]-1 ]=P[ next [ i-next[ i-1 ]-1\) ~ \(i-1]]\)

这时沿用上述的思路 如果\(P[next[next[i-1]]]=P[i],\)也可以\(next[i]=next[next[i-1]]+1\)

否则,我们可以继续是否存在\(next[next[next[..]]]\)是成立,直到\(next[next[...]]=0\)

  • code
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;
const int maxn=1e6+10;
char a[maxn],b[maxn];
int nex[maxn]={0};
int top=0;
int main(){
	ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);
	cin>>(a+1)>>(b+1);
	int lena=strlen(a+1),lenb=strlen(b+1);
	for(int i=2,j=0;i<=lenb;++i){
		while(b[i]!=b[j+1] && j) j=nex[j];
		if(b[j+1]==b[i]) ++j;
		nex[i]=j;
	}
	for(int i=1,j=0;i<=lena;++i){
		while(j&& b[j+1]!=a[i]) j=nex[j];
		if(b[j+1]==a[i]) ++j;
		if(j==lenb) {
			cout<<i-lenb+1<<endl;
			j=nex[j];
		}
	}
	for(int i=1;i<=lenb;++i) cout<<nex[i]<<" ";
	return 0;
}
posted @ 2021-08-29 17:26  归游  阅读(76)  评论(0)    收藏  举报