前言
其实很早就学了,不过还是感觉很迷。但做到了NOIP2020的T2,只有学一下了。
原理
KMP是由3名数学家搞出来的算法,KMP也就是这3位的首字母。
模板传送门luoguP3375
首先看题目要求给定2个字符串,要求其中的一个字符串在另一个字符串里出现的次数。
容易想到暴力:
直接枚举2个串,如果不同,就将去匹配的串从头开始重新从匹配失败的位置继续找。
最坏的时间复杂度是:O(nm)
这个暴力算法叫做BF算法。
我们发现这个算法最主要的问题是每次从头开始重新匹配,其实可以从其中的某个地方开始找,就能减少大量的复杂度。
例如:
s1:ABCABCABD
s2:ABCABD
我们匹配到第6位时匹配失败,此时不难发现:
s1:ABCABCABD
s2:ABCABD
s2可以直接接到加粗的AB的位置继续匹配。
s1:ABCABCABD
s2:----- ABCABD
由于我们搜到了第6位,说明前面的全部是相等的,所以可以不必比较s1和s2,直接在s2上比较前缀和后缀是否相同,由于全部相同,所以就等价在s1上找。
现在就是要找到由于是按顺序找,所以就是找A,AB,ABC,ABCA,ABCAB,ABCABD的前后缀是否相同,使匹配失败后跳转。注意:这里的跳转是指从哪一位开始继续枚举。
通常我们使用nex数组表示失败后要从哪一位开始枚举。
给出通项公式:
n e x [ i ] = { − 1 j = 0 l e n 相 同 的 前 后 缀 的 长 度 0 前 后 缀 不 同 nex[i]=\begin{cases}-1 \qquad \;j=0\\len \qquad 相同的前后缀的长度\\0 \qquad\;\;\; 前后缀不同\end{cases} nex[i]=⎩⎪⎨⎪⎧−1j=0len相同的前后缀的长度0前后缀不同
代码实现:
void getnex(){
int k=-1,j=0;
nex[0]=-1;
while(j<tlen){
if(k==-1 || t[k]==t[j]) nex[++j]=++k;
else k=nex[k];
}
}
匹配也差不多,当发现匹配的字符串已经全部匹配时,就说明匹配好了,匹配的起点就等于扫过了文本串的长度减去模式串的长度加+1。
代码实现:
void KMP(){
int i=0,j=0;
while(i<len && j<tlen){
if(j==-1 || a[i]==t[j]) i++,j++;
else j=nex[j];
if(j>=tlen) cout<<i-tlen+1<<"\n",j=nex[j];
}
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int nex[N],tlen,len,ans[N],cnt;
string a,t;
void getnex(){
int k=-1,j=0;
nex[0]=-1;
while(j<tlen){
if(k==-1 || t[k]==t[j]) nex[++j]=++k;
else k=nex[k];
}
}
void KMP(){
int i=0,j=0;
while(i<len && j<tlen){
if(j==-1 || a[i]==t[j]) i++,j++;
else j=nex[j];
if(j>=tlen) cout<<i-tlen+1<<"\n",j=nex[j];
}
}
int main(){
cin>>a>>t;
len=a.size();tlen=t.size();
getnex();
KMP();
for(int i=1;i<=tlen;i++) cout<<nex[i]<<" ";
return 0;
}