kmp算法详解:https://blog.csdn.net/qq_41661809/article/details/81415687
这里有算法实现的动图->https://blog.csdn.net/qq_37969433/article/details/82947411
KMP算法要解决的问题就是在字符串(也叫主串S)中的模式(pattern)定位问题。说简单点就是我们平时常说的关键字搜索。如果它在主串中出现,就返回它的具体位置,否则返回-1(常用手段)。
在暴力的解法中,我们从左至右一个个匹配模式串,失配就回溯到起点的下一位,然后重新匹配。但这种思路如果遇到这种情况:在主串“SSSSSSSSSSSSSA”中查找“SSSSB”,每次比较到最后一个才知道不匹配,然后回溯,这个的效率是显然是最低的。(O(n*m))
(盗图+1 (*゚∀゚)=3 )
像马拉车算法中利用了回文串左右对称的特点一样,kmp利用已经部分匹配这个有效信息,保持i指针不回溯,通过修改j指针,让模式串尽量地移动到有效的位置,从而大幅度优化了最初暴力的思路。(因为前面和模式串匹配过了,我们可以先处理模式串,然后直接判断回溯到哪个位置最终才可能匹配成功,不可能匹配成功的就不用回溯到那个位置了)
如上图所示的应该是最容易理解的情况了,匹配到第3位时,发现A和E不匹配,但前面1~2的子串中都不含A,(模式串的首位字符是A)所以下一次可以直接从第3位开始重新匹配,如下图:(j:3–>0)
当前面的子串中含有模式串的首字母呢?
如上图,若此时失配,前面1~8的子串中,可以从3,4,6的位置开始匹配,但只有从位置6开始才能匹配到第8位(即第j-1位),最终才有可能匹配成功。也就是说j回溯到的位置k要满足P[0 ~ k-1] == P[j-k ~ j-1]。所以k是“ABCAABABC”的前缀串和后缀串相同时可以达到的最大长度
然后就剩下最后一个问题:怎么求这些位置j对应的k,使之满足P[0 ~ k-1] == P[j-k ~ j-1]。
next数组
通过上面的结论可以知道,下图中next[15]=6,
1、当P[15]==P[6]时,
next[16]=7,
也就是说,当P[15]==P[next[15]]时,P[16]=P[15]+1
—————当p[
j
j
j]==p[next[
j
j
j]]时,next[
j
j
j+1]=next[
j
j
j]+1
2、当P[
j
j
j]!=P[next[
j
j
j]]时呢,如下图P[16]='B’时,P[16]!=P[7]
或者说P[7]与P[16]失配了。P[7]失配,那就往前找:
突然想起之前一个dp题:Largest Rectangle in a Histogram(DP!),一直往回走,直到找到比它的高度低的矩形,但一直没有理解为什么它们的时间复杂度是O(m+n)
求next数组代码:
char s[manx],p[manx];
//s为主串,p为模式串,从0位开始存
int net[manx],ls,lp;
int getnext()
{
int j=0,k=-1;
net[0]=-1;
//因为每次找到符合条件的k值时,next[j+1]=k+1;但j最少可以回溯到0,所以net[0]要初始化为-1
while(j<lp-1)
//最后处理的一个数是next[j+1],所以j循环上限为lp-2(j是先加,再更新next[j]的值)
{
//直到P[k]==P[j]时,得到P[j+1]的值
if(k==-1||p[k]==p[j])
{
j++;
k++;
net[j]=k;
}
else
k=net[k];
}
}
next数组处理完后,就可以在i不回溯的情况下匹配模式串了:
int kmp()
{
getnext();
int i=0,j=0;
while(j<lp&&i<ls)
//j==lp表示寂静匹配成功了
//i==ls表示主串已经遍历完了
{
if(j==-1||s[i]==p[j])
i++,j++;
else
j=net[j];
}
if(j==lp) return i-lp;
//看下图就知道为什么是i-lp了
else return -1;
}
例题:P3375 【模板】KMP字符串匹配
题目给出两个字符串s1、s2,要求输出s2在s1中所有出现的位置。并输出子串的前缀数组 next。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
//#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define mid ((l + r)>>1)
#define chl root<<1
#define chr root<<1|1
using namespace std;
typedef unsigned long long ull;
typedef long long LL;
const int manx=1e6+10;
char s[manx],p[manx];
int net[manx],ls,lp;
void getnext()
{
int j=0,k=-1;
net[0]=-1;
//处理到了next[lp],方便后面匹配出所有情况
while(j<lp)
{
if(k==-1||p[k]==p[j])
net[++j]=++k;
else
k=net[k];
}
}
void kmp()
{
getnext();
int i=0,j=0;
//因为要找到所有位置,所以不用加j<lp的条件
while(i<ls)
{
if(j==-1||s[i]==p[j])
i++,j++;
else
j=net[j];
if(j==lp)
printf("%d\n",i-j+1);
}
}
int main()
{
scanf("%s%s",s,p);
ls=strlen(s);
lp=strlen(p);
kmp();
for(int i=1;i<lp;i++)
printf("%d ",net[i]);
printf("%d\n",net[lp]);
return 0;
}