题目地址:KMP
给定一个字符串 S,以及一个模式串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模式串 P 在字符串 S 中多次作为子串出现。
求出模式串 P 在字符串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤10^5
1≤M≤10^6
输入样例:
3
aba
5
ababa
输出样例:
0 2
KMP算法总得来说就是在暴力的基础上做一些优化,减少匹配串 p 后移的长度,而不是每次从 p 的第一个字符开始重新匹配。那么该将 p 后移多少位置呢?如果令匹配失败的位置为 j + 1,将 p 后移(也即指针 j 前移)之后即将与 i 进行匹配的位置记为 j' + 1,应该满足
p[1~j'] == p[(j-j'+1) ~ j]
即 匹配串 p 的长度为 j' 的前缀 与 匹配串 p 的长度为 j' 的后缀 完全相同,我们将 j 修改为 j' 而不是 0,节省了 j' 长度的匹配。j' 便是匹配串 p 截止到 j 的最大相等前后缀长度,这个长度只与匹配串 p 有关,我们可以用 O(n)时间求出不同长度的 p 的最大相等前后缀长度,记在 ne 数组(next 数组)中,若发生不匹配,我们将 j 修改为 ne[j] 继续匹配即可。
#include <iostream>
using namespace std;
const int N = 100010;
const int M = 1000010;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;
//j指向匹配串中最后一个匹配上的字符,每次拿i和j+1匹配
//求next
for (int i = 2, j = 0; i <= n; ++ i){ //求next实际上是拿匹配串p去匹配自己,ne[1] = 0,所以i从2开始
while (j && p[i] != p[j + 1]) j = ne[j]; //不匹配,向后移动匹配串p,也即向前移动j
if (p[i] == p[j + 1]) ++ j; //匹配上了,j后移,i在当前循环结束后也后移
ne[i] = j; //j是匹配串后移之后当前匹配上的字符数,也即当前前缀长度,也即最大相等前后缀长度
}
//匹配s
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){ //匹配成功
j = ne[j]; //移动匹配串p,后面继续匹配
printf("%d ", i - n);
}
}
return 0;
}