为什么要学它?
因为字符串问题匹配用它,相比暴力,复杂度可以从O(m*n)降到O(m+n),而且其中求最长相等前后缀的方法可以解决很多问题。
介绍一下字符串匹配:给出两个字符串a和b,要找出在a中的b。
如a为:abcacabcab,b为:abcab
正常暴力是逐位匹配,但是这样在每一次失配后都要重新匹配,这会增加很多的时间复杂度。
而KMP算法就可以减少损失的时间复杂度,因为KMP算法在每一次失配后不会重新匹配,而会跳到更优的位置(b的位置)匹配。
而这个更优的位置怎么求?这就要引入一个新的数组,即为next(实际上为了避免编译时出现的奇奇怪怪的bug经常写成“nxt”)。该next[i]即为在b匹配到第i个字符时失配时应从b的哪个位置开始继续匹配。
例如上述例子,匹配到如下(加粗字母b)时失配:
abcacabcab
abcab
我们可以不从头开始匹配,而是从如下开始继续匹配(b中的'a'和中的'a'对齐(已加粗)):
abcacabcab
abcab
这样即为next[5]=1
那么,这个next数组应该怎么求呢?
这就要引用上文提到的最长相等前缀和了。
为什么要利用它?因为当我们如果匹配到第i位时失配,第i位之前的信息我们都是知道的,所以我们可以通过处理b数组的第i位之前的字符,来得到失配后该从哪里重新开始匹配。显然,我们可以利用b的前i-1位的最长相等前后缀和来判断,这个模拟一下就可以理解,也可以参照上面失配时的示范。
如何用代码实现?可以用b自己匹配自己,这个不太好理解,就在代码里注释了,如下:
p=0;
for(int i=2;i<=lenb;i++)
{
while(b[i]!=b[p+1]&&p) p=next[p];//失配就往回跳,直到可以匹配
if(b[i]==b[p+1]) p++;//匹配成功,则+1
next[i]=p;//记录next数组
}
这段代码如果不理解可以输出一下来帮助理解。
next数组求完后,就可以按之前说的步骤进行了。
代码如下:
p=0;
for(int i=1;i<=la;i++)
{
while(a[i]!=b[p+1]&&p) p=next[p];//不断跳next直到能匹配
if(a[i]==b[p+1]) p++;
if(p==lb)//匹配成功
{
ans++;//统计结果
p=next[p];
}
}
这样KMP就完成了。
洛谷的模板题可以做一下传送门
(附上这道题的代码)
#include<bits/stdc++.h>
using namespace std;
char a[1000005],b[1000005];
int next[1000005],p;
int main()
{
cin>>a+1>>b+1;
int la=strlen(a+1),lb=strlen(b+1);
for(int i=2;i<=lb;i++)
{
while(b[i]!=b[p+1]&&p) p=next[p];
if(b[i]==b[p+1]) p++;
next[i]=p;
}
p=0;
for(int i=1;i<=la;i++)
{
while(a[i]!=b[p+1]&&p) p=next[p];
if(a[i]==b[p+1]) p++;
if(p==lb)
{
cout<<i-lb+1<<endl;
p=next[p];
}
}
for(int i=1;i<=lb;i++) cout<<next[i]<<" ";
return 0;
}
(蒟蒻的第二篇,求支持qwq)