芝士:KMP
kmp
引例
过程
考虑普通的暴力的过程,
其复杂度主要是卡在模式串只向右边移动一个位置
很明显,这忽略了串内部的联系
就比如模式串\(aaabaaac\),如果在比较c的时候出现了错误,真的需要从头开始比较么?
很显然,可以从\(b\)开始进行再一次的比较
那么可以考虑求出以\(i\)为尾端点的后缀所能匹配到的最长前缀
就比如上一个模式串中的\(nxt[7]=3\)(字符串从1开始)
如果已经求出了\(nxt\)数组,就可以利用上面所说的过程进行匹配
那么现在的问题就在于怎么求出\(nxt\)数组
如果\(t[nxt[i-1]+1]==t[i]\),那么就直接有\(nxt[i]=nxt[i-1]+1\)
如果没有,也是考虑用匹配的思路去进行计算,即我可以考虑\(t[nxt[nxt[i-1]]+1]==t[i]\)
很明显,用反证法可以说明这一定是最长的前缀
就比如$abacabab $
很明显有\(nxt[7]=3\)
但是发现\(t[4]!=t[8]\)
故用\(nxt[3]+1\)来匹配\(t[8]\)
因为前缀和后缀是一样的,所以算法的正确性也不难证明
时间复杂度
求nxt
考虑总共的转移过程,第一次一定是从\(nxt[i-1]\)开始
考虑如果要\(nxt[i]\)变大,那么此时只会提供\(O(1)\),即只会在前一个+1
考虑\(nxt[i]\)变小,那么一定是在\(nxt[i-1]\)上进行操作
可以看作拔高和降低操作,一定是有高度才能降低
故总的时间复杂度为\(O(n)\)
匹配
考虑模式串在文本串中的已经匹配的区间为\([l,r]\)(文本串中的区间)
如果下一个字符可以进行匹配,那么\(r\)就会往右移动,\(l\)不动
如果下一个字符不能进行匹配,那么\(l\)就会往左移动,\(r\)不动
考虑每一次动都至少会动1个单位
所以时间复杂度为\(O(n)\)
代码
#include<iostream>
#include<cstring>
using namespace std;
char t[1000005];//文本串
char s[1000005];//模式串
int nxt[1000005];//失配数组
int lens,lent;
int main()
{
cin>>(t+1)>>(s+1);
lent=strlen(t+1);
lens=strlen(s+1);
int j=0;
for(int i=1;i<=lens;i++)
{
while(s[j+1]!=s[i]&&j)
j=nxt[j];
if(s[j+1]==s[i]&&i!=j+1)
j++;
nxt[i]=j;
}
j=0;
for(int i=1;i<=lent;i++)
{
while(t[i]!=s[j+1]&&j!=0)
j=nxt[j];
if(t[i]==s[j+1])
j++;
if(j==lens)
{
cout<<i-lens+1<<'\n';
j=nxt[j];
}
}
for(int i=1;i<=lens;i++)
cout<<nxt[i]<<' ';
return 0;
}
exkmp
咕咕咕

浙公网安备 33010602011771号