给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤105
1≤M≤106
输入样例:
3
aba
5
ababa
输出样例:
0 2
首先我要推荐两个博主的解析,可以按顺序看,第一个是大概原理,然后第二个是比较详细的原理讲解,然后我又回去看了算法基础课的视频,觉得还是挺有帮助的
1.字符串匹配的KMP算法--前缀和后缀的详解_Wait for 的专栏-CSDN博客_kmp前缀后缀指的是什么
2.从头到尾彻底理解KMP(2014年8月22日版)_结构之法 算法之道-CSDN博客_kmp
对于第二个链接,我可以就是说两个点,ne数组的值就是存的前一个数的前缀后缀最大匹配值,然后就是ne数组匹配的时候,当匹配不上的时候,k = ne【k】(这个可以在第二个链接找详解)
然后下面的代码的ne【】数组是当前数的前缀后缀最大匹配值
我在纸上写了下运行过程
这个运行样例有点特殊,因为它不存在匹配失败的情况。但是我不想它特殊,我就把详解的字母把空格去掉,就是7和21位字母作为模板串和模式串进行分析。
这个样例的过程花了我好多张纸,就不拍出来了,可以自己找画图软件啥的写一下,我不喜欢画图软件,我就在纸上写了。但是经过我很长时间的推理之后,发现模式串也存在特殊情况,因为最后不匹配的值正好是模板串的第一个数,所以我对模式串做了删改,改成了BBCABCDABCDABDE,这次是十五个字母,然后我在找规律。虽然挺麻烦的,但是毕竟我是刚学,能弄懂就好了。
然后我把这个过程总结一下,上面的图片应该看不清(反正我就是用这个记录我的学习),但是这个样例我觉得是很好的
先说ne【】数组的取值,这里是每个下标的前缀后缀最大匹配值,当自身能匹配上的时候,则左指针值加一,相当于在i这个点,前缀后缀最大匹配值为j,然后当不匹配的时候,把坐标为j的前缀后缀最大匹配值赋值给j,一直到j=0或匹配成功,在这里是直接j变成0了,所以最后ne【7】=0,如果第7位数能匹配成功的话,那前缀后缀最大匹配数就会增加。
ne【】数组赋值完成后,就是模式串和模板串的匹配了,匹配是i匹配j+1,定义i=1,j=0,在这里我想说明一点,就是这个输出取决于j什么时候能成为模板串的长度,在j没有达到模板串的长度时,匹配成功一次,j+ 1 , 在匹配失败的时候,j就变成下标为j的前缀后缀最大匹配值,这里就相当于已经匹配成功了几位数,要找在已经匹配成功基础上,是否能继续匹配成功,然后继续这个过程,直到j=模板串的长度,然后输出i-length(模板串)(这个时候就是输出模式串的以0为下标,第一个与模板串相匹配的下标)
在输出之后,将模板串的前缀后缀最大匹配值赋值给j,看剩下的模式串还有没有机会再出现与模板串相同的组合。
这个是答案代码:-)
#include <iostream>
using namespace std;
const int N = 100100, M = 1000100;
int n, m;
int ne[N];
char s[M], p[N];
int main()
{
cin >> n >> p + 1 >> m >> 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)
{
printf("%d ", i - n);
j = ne[j];
}
}
return 0;
}