KMP算法解决什么问题呢?
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
或者是,寻找S串中是不是存在P
我们都能很轻松的想到,如果我们要匹配,我们可以把P串的第一位对应S串的每一个字符来进行匹配,如果匹配成功了就匹配P的第二个字符和当前匹配好的S的下一个来再进行匹配;如果中途不能匹配了,就重新从P的头匹配刚才S进行匹配的下一个字符(可能描述不准确,就是暴力匹配
那么相当于我们每次都把P进行向后移动一位。
那么我们实际上是可以知道,它最多往后到什么时候能继续进行匹配?
假设现在是不能匹配的时候,到了黄色圈那个字符不能匹配了
那我这个时候就应该把红色的模版串往后移使得他们能够继续匹配
假设移动到了这里
这时候能匹配上了(当然可能不会有重复的),然后我们能观察发现
这个时候黄色标注的区域是相等的
绿色的又是P前面的那些
那么这相当于什么,相当于我们在该串中,他的以右边黄色为结尾的该子串的前缀和后缀是相等的(注意这里后缀只是结尾是一定的,比较的时候也得从左往右比较相等)
那么,前缀和后缀相等的最大长度,就是我们从前往后移动P的最短距离
我们设next数组表示, 模式串中每个前缀最长的能匹配前缀子串的结尾字符的下标
next[i] = j 表示下标以i - j + 1为起点,i为终点的后缀和下标以1为起点,j为终点的前缀相等,且此字符串的长度最长
即p[1,j] = p[i - j + 1, i]
那么我们前面的那些都匹配过了,就肯定知道前面与S串的部分是相同的,而往后移动的距离就是 j j j
因为我们要让他们匹配相等
在这里插入图片描述
而这里对next的含义解释实际上就是我们匹配到这里下一个从这里匹配就行
匹配的过程就是:
// kmp匹配过程
for(int i = 1, j = 0; i <= m; i++){ // 枚举当前的s[i],和匹配的是p[j+1]
while(j && s[i] != p[j + 1]) // j没有退回起点
j = ne[j]; // 如果还不能匹配,那么我看我新的这个点的下一个点能不能和s[i]匹配
// 为什么是下一个点呢,因为我们根据next数组的定义就知道,当前j肯定是和前面相等的(不知道我这里解释的对不对,还请看到的大佬指正)
if(s[i] == p[j + 1]) j++; // 如果匹配了就可以匹配下一个位置
if(j == n){
// 成功
}
}
求next过程,我们根据定义就可以知道,实际上求next并不需要S串
如何求next呢?
我们匹配i-1和j,如果不相等,我们退而求其次,即再匹配ne[j]和i是不是相等的,如果不行就再退
// next求解
// next[1] = 0,如果第一个字母失败了,只能从0开始
for(int i = 2, j = 0; i <= n; i++){
while(j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) j++;
ne[i] = j;
}
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
int n, m;
char p[N];
char s[M];
int ne[N];
int main(){
cin >> n;
scanf("%s", p + 1);
cin >> m;
scanf("%s", s + 1);
for(int i = 2, j = 0; i <= n; i++){
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<< " "; // 全部匹配完了,而且全部匹配上了,用当前的点减去模版串的长度就是起点了
j = ne[j]; // 匹配成功再往后退一步来看剩下的j的后缀还有没有与P相同的
}
}
}