KMP算法
KMP算法简介
KMP是一种支持单模式匹配的字符串匹配算法(说人话:匹配一个字符串是否在另一个字符串中出现
暴力解法
我们可以考虑O(n^2)的暴力算法
假如我们要在字符串a中匹配字符串b
枚举a字符串的每一个字符后|b|个字符,O(|b|)的时间复杂度判断是否与b重合
考虑优化
为什么我们的复杂度如此之高
究其原因,对于a的每一个字符,我们都从头开始匹配b串,我们可以进行剪枝,当我们找到一个字符不匹配后,我们可以直接取匹配下一个位置,这个剪枝效果很明显,但会出现一中情况让复杂度退化到O(|b|)
a串:aaaaaaaaa
b串:aaaaab
我们可以手模一下,对于a的每一位,我们都匹配了|b-1|次
手摸以后我们就能很明显的发现,b串的aaaaa这个子串被反复匹配了,这里我们发现:

在上一次匹配中我们匹配了黄色部分,匹配失败后,下一位的红色部分和上一位完全一样,被重复匹配,就加大了时间复杂度
我们可以对b串预处理一个nxt数组,我们用\(nxt_i\)表示b串前i个字符最长的相同的前缀和后缀,可以看下图理解
此时,我们b串前五个字符中的aba即是前缀又是后缀,那么b串的\(nxt_5\)就等于3
这有什么用呢,假如我们匹配时匹配到了b串的第五位,我们在下一位匹配时可以直接从\(nxt_5\)即第3位开始匹配
我们再次考虑复杂度,当我们处理完nxt数组后,我们只需要从1到|a|匹配一次a串就行了,具体而言,对于每一位串有两种情况
假如我们的a串匹配到第i位,b串匹配到第p位
- \(a_{i+1} = b{p+1}\)我们让i和p都后移一位
- \(a_{i+1} \not= b_{p+1}\)我们让p移到\(nxt_p\)然后重复判断情况1,如果不匹配,就接着往后跳
这样,我们的时间复杂度就是O(|a|) 的
nxt数组的求解
假如我们现在考虑到b串的第i位,前i-1位的nxt数组已经求解完毕
其实我们可以像匹配时一样,由nxt数组定义可得
\[b_1-b_{nxt_{i-1}}=b_{i-nxt_{i-1}}-b_i
\]
所以我们求解nxt数组的过程可以类比匹配,我们定义变量p=i-1
- \(b_{p+1}=b_i\)这时候nxt_i就等于 p+1
- \(b_{p+1} \not= b_i\),让p=nxt_p
直到p==0时我们匹配不到,就返回0,否则我们一定匹配过nxt_i了
模板代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 1000010
inline ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
char s1[MAXN],s2[MAXN];
ll nxt[MAXN];
ll len1,len2,now;
vector <ll> vec;
int main(){
cin>>(s1+1)>>(s2+1);
len1=strlen(s1+1),len2=strlen(s2+1);
// cout<<len1<<" "<<len2<<endl;
// cout<<(s1+1)<<" "<<(s2+1)<<endl;
for(int i=2;i<=len2;i++){
ll p=nxt[i-1];
while(p&&s2[p+1]!=s2[i])p=nxt[p];
nxt[i]=(s2[p+1]==s2[i]?p+1:0);
}
for(int i=1;i<=len1;i++){
while(now&&s2[now+1]!=s1[i])now=nxt[now];
now=(s2[now+1]==s1[i]?now+1:0);
if(now==len2)vec.push_back(i-len2+1),now=nxt[now];
}
for(int i=0;i<vec.size();i++)cout<<vec[i]<<endl;
for(int i=1;i<=len2;i++)cout<<nxt[i]<<" ";
return 0;
}

浙公网安备 33010602011771号