KMP字符串

KMP字符串问题的思想

给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字,模板串P在模式串S中多次作为子串出现,求出模板串P在模式串S中所有出现的位置的起始下标。

输入格式

第一行输入整数N,表示字符串P的长度。 第二行输入字符串P。 第三行输入整数M,表示字符串S的长度。 第四行输入字符串S。

输出格式

共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

using namespace std;

const int N = 1e4 + 10, M = 1e5 + 10;

char p[N], s[M];
int ne[N];

int main()
{
int n, m;
cin >> n >> p + 1 >> m >> s + 1;
for (int i = 2, j = 0; i <= n; i++)
{
while (j && p[j + 1] != p[i])
j = ne[j];
if (p[j + 1] == p[i])
j++;
ne[i] = j;
}
for (int i = 1, j = 0; i <= m; i++)
{
while (j && p[j + 1] != s[i])
j = ne[j];
if (p[j + 1] == s[i])
j++;
if (j == n)
{
j = ne[j];
printf("%d ", i - n);
}
}
return 0;
}

思想:

我们可以从暴力入手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

using namespace std;

const int N = 1e4 + 10, M = 1e5 + 10;

char p[N], s[M];
int ne[N];

int main()
{
int n, m;
cin >> n >> p + 1 >> m >> s + 1;
for (int i = 1; i <= m; i++)
{
bool flag = true;
for (int j = 1; j <= n; j++)
{
if (s[i + j - 1] != p[j])
{
flag = false;
break;
}
}
if (flag)
cout << i - 1 << " ";
}
return 0;
}

可以发现,在进行比较时,上一次比较时的信息可以用来优化这次模板串的移动 即转换成模板串最少往后移动多少位的问题 需要预处理模板串,计算出模板串每一位作为后缀子串的终点,与该后缀子串相等的前缀子串最大长度为多少 我们可以创建一个next数组来储存(关键) ne[i]表示以i点为后缀子串终点,相等的前缀子串的最大长度 next数组的创建 和 模式串与模板串的比较过程 的具体实现方法为双指针算法(举例体会)

注:

  • 在两串比较成功一次后,需要一个 j = ne[j]; 的操作,是为了在比对模式串的下一位时模板串移动最少的位数
  • 在创建next数组时 i 从2开始,ne[1] = 0