KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。(百度的嘻嘻)
KMP难在对于next数组的构建与理解
next[i]的定义是 到 i 处已找到的最长相同前后缀的长度
next的作用是当字符串s1和字符串s2匹配到s1[i]和s2[j]时, 若s1[i]和s2[j]不相等,就不必再把j重新从0开始找,而是跳到和当前指针j有相同前缀的地方。
s1[i]和s2[j]不相等,但之前有一部分是相同的
举个例子:比如说 ababc 和 ababd, 比较到c和d时发生了冲突,但这时发现d的前面有一个ab, c的前面也有一个ab,这时候我们只要跳到前一个ab处就可以继续比较
ababc 和 ababd 中的 c和a了, 省了不少时间
首先我们要理解一波前缀和后缀, 前缀和后缀指的是一个集合
例如 ababa
其前缀为{a,ab,aba,abab}(不包括最后一个)
后缀为{a,ba,aba,baba}(不包括第一个)
可以很清楚的看到,最长前后缀就是 aba, 长度为3
我个人理解的next[i]是跳到与i的后缀 有最长的相同前缀的地方
贴一份next数组构建的代码, 也就是s2的自我匹配
for(i = 1; i < bn; i++){
while(j && s2[j]!=s2[i]) j = next[j-1];//指针跳到j-1的最长前缀处
//s2[j]!=s2[i],但是既然j>0,说明从s2[0]到s2[j-1]==s2[i-j]到s2[i-1],这时跳到新的j,新的s[j-1]也会等于原来的s[j]
if(s2[j]==s2[i]){
next[i] = ++j;//next[i] = 当前指针(因为字符串是从0开始的,而指针到j最长前缀为j-1)+1, 同时 j往下移一个指针
}
}
然后贴出完整代码:
#include <cstdio>
#include <cstring>
using namespace std;
int next[1001];
char s1[1000001], s2[1001];
//next[i]指 到 i 处的最长前后缀
int main(){
int i, j = 0, an, bn;
scanf("%s%s", s1, s2);
an = strlen(s1); bn = strlen(s2);
for(i = 1; i < bn; i++){
while(j && s2[j]!=s2[i]) j = next[j-1];//指针跳到j-1的最长前缀处
//s2[j]!=s2[i],但是既然j>0,说明从s2[1]到s2[j-1]==s2[某个位置开始]到s2[i-1],这时跳到新的j,新的s[j-1]也会等于原来的s[j]
if(s2[j]==s2[i]){
next[i] = ++j;//next[i] = 当前指针(因为字符串是从0开始的,而指针到j最长前缀为j-1)+1, 同时 j往下移一个指针
}
}
j = 0;
for(i = 0; i < an; i++){
while(j && s1[i] != s2[j]){
j = next[j-1];//如果目前的s1[i]和s2[j]不相等就一直跳到最长前缀处, 直到相等就接着查找
}
if(s1[i] == s2[j]) j++;//相等的话就移动s2[]的指针
if(j == bn){
printf("%d\n", i-j+2);//在第几个字母找到的
j = next[j-1];//跳到上一个位置的next处,这样可以下次直接比较s1[i]和s2[j]
}
}
for(i = 0; i < bn; i++) printf("%d ", next[i]);
return 0;
}
可以去B站搜索KMP算法,印度小哥讲的超级详细
链接:https://www.bilibili.com/video/av3246487/?from=search&seid=17380554719454557667