建议搭配着董晓算法的视频观看此题解。视频链接:F03【模板】KMP 算法_哔哩哔哩_bilibili
声明:本题解在董老师讲解的基础上再进行细分,如有看不懂的地方,请在评论区提问或者私信我。如图文有侵权,请联系我删除
题目链接:【模板】KMP - 洛谷
我们先从一个样例去代入这道题。这里假设S是主串,P是模式串,求P串在S串中出现的位置。(PS:这里字符串的下标从1开始)
那么我们先考虑暴力算法,肯定是S和P一个一个的对比。像这样
一直对比到出现第一个不相同的字母的时候。这个时候嘛肯定是,i要回到S的第二个位置,然后j回到第一个位置,像这样
我们想想都知道,这时间复杂度太高了(这里假设S的长度是m,P的长度是n,那么时间复杂度就是O(nm))非常的高。并且你想想,前面S和P都有这么多已经匹配的字母了,就因为一个不相同的字母就导致了j从9回到1,i从9回到了2,这太可惜了吧。
那么KMP算法就是为了解决这样的问题,让浪费最小化
KMP算法呢,我建议还是看看董老师讲解的,我在这里只解释一下视频中的一些难点
我这里只解释一下为什么每次P[i]!=P[j+1]的时候,j需要等于ne[j]。
首先我们知道next[i]表示的是模式串P[1,i]中相等前后缀的最长的长度(图片中ne数组即是next数组)。
我们以右边中间的那幅图来举例子(i=8,j=5的那幅图)。在这幅图中,我们已经找出了相等前后缀的最长长度,那么当i增加到9的时候,这个时候P[9]=a,我们在看P[j+1](j==5)即P[6]=b,
这个时候P[9]!=P[6],那么说明此时相等前后缀的长度不能+1,那么只能减少,这一点是毋庸置疑的。接来下就是重点:那么上面新加进来的字母a,会导致此时的相等前后缀长度减少,那么下面的aabaa想要维持原来的状态,原来是什么状态呢,就是下面的aabaa和上面的aabaa,那个时候的相等前后缀长度是5,但是你上面新加进的a肯定会破坏原本的状态,此时下面的aabaa想要维持原来的长度,或者说让自己减小的小一点。或者说此时上面的aabaa和下面的aabaa不匹配了,因为上面的aabaa的下一个和下面的aabaa的下一个不匹配嘛,所以这个时候下面的aabaa的最后一个a就要跳回到一个匹配的位置,这个时候我们在看一下ne[i]的定义,他表示模式串P[1,i]中的相等前后缀的长度,所以j要回跳到ne[j]的位置,因为这个位置是上面的aabaa和下面的aabaa,或者说不放弃已经匹配了这多的字符嘛,然后这个时候再看ne[j]+1的位置是不适和上面的aabaa的下一个位置匹配,这就是为什么j要跳回到ne[j]的原因,说得有点抽象,希望大家可以补充一下,或者有疑问的可以再评论区留言,我有时间的话会回复的!
最后放出代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1000010;
int m,n;
char S[N],P[N];
int ne[N];
int main()
{
cin>>(S+1)>>(P+1);
m = strlen(S+1),n = strlen(P+1);
ne[1] = 0;
for(int i = 2,j = 0;i<=n;i++)//next函数计算
{
while(j && P[i]!=P[j+1]) j = ne[j];
if(P[i] == P[j+1]) j++;
ne[i] = j;
}
for(int i = 1,j = 0;i<=m;i++)
{
while(j && S[i]!=P[j+1]) j = ne[j];
if(S[i] == P[j+1]) j++;
if(j == n) cout<<i-n+1<<endl;
}
for(int i = 1;i<=n;i++) cout<<ne[i]<<' ';
}