KMP算法
Posted 归游
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法相关的知识,希望对你有一定的参考价值。
模板】KMP字符串匹配
KMP字符串匹配用于两个字符串中,是否一个字符串是另一个的子串
我们称其中两个串一个为S(长度为n),一个为P(长度为m),问是否P为S的字串
这种题暴力的想法很容易想到但时间复杂度为\\(O(nm)\\)
其实KMP也就只是在暴力的想法上进行了优化,不过优化的程度很高
举例
S:a b a d a b a
P: a b b
暴力思路就是枚举主串的每个字符,然后从枚举到的字符后m个字符是否有一样即可
而暴力的思路明显存在缺点,一旦匹配失败就只右移一位,然后从头继续匹配
而KMP的思路是移动多位(那么此时同学会想,移动多位会不会丢失一些可能可以匹配的字符串?且接着往下看)
这个疑问非常有道理,所以KMP的算法妙就妙在这里,跳过一些不可能匹配成功的位置
这个问题被很好的解决,通过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[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;
}
以上是关于KMP算法的主要内容,如果未能解决你的问题,请参考以下文章