KMP(Knuth-Morris-Pratt)字符串匹配算法的基本思想是提高字符串查找效率,避免在文本串中回溯那些已经知道不会匹配的位置。它通过构建一个部分匹配表(也称为前缀函数或失败函数),在模式串中查找与文本串匹配的子串时,能够利用已有的匹配信息,从而减少不必要的比较。
基本思想:
- 前缀函数(ne):对于模式串
p
,前缀函数ne
表示在模式串中,每个位置i
的最大长度j
,使得p[0...j]
是p[0...i-1]
的后缀子串。换句话说,当文本串中的当前字符与模式串中的p[i]
不匹配时,ne[i]
告诉我们下一步应该将模式串向右移动多少位。 - 匹配过程:在文本串中查找模式串时,当发生不匹配,我们可以根据
ne
值直接跳转到模式串中的下一个匹配起点,而不是简单地将模式串向右移动一位。
操作步骤:
-
构建前缀函数:
- 初始化
ne[0]
为 -1,表示没有匹配的前缀。 - 遍历模式串
p
的每个位置i
(从 1 开始),维护一个变量j
表示当前已匹配的前缀长度。 - 如果当前字符匹配(
p[i] == p[j + 1]
),则增加j
。 - 如果字符不匹配,根据
ne[j]
来更新j
的值,即跳过已经知道不会匹配的前缀部分。 - 将
ne[i]
设置为j
。
- 初始化
-
文本串匹配:
- 初始化文本串
s
的索引i
为 1。 - 遍历文本串的每个位置
i
。 - 对于每个
i
,维护模式串的索引j
为 0。 - 当文本串中的字符与模式串中的
p[j + 1]
匹配时,增加j
。 - 如果发生不匹配(
p[j + 1] != s[i]
),根据ne[j]
更新j
的值。 - 当模式串完全匹配(
j == n
)时,输出匹配的起始位置i - n
,并更新j
为ne[j]
以继续查找下一个匹配。
- 初始化文本串
通过这种方法,KMP 算法确保每次匹配失败后,模式串至少向右移动一位,而不是重头开始匹配,从而提高了字符串匹配的效率。
问题描述
- 给定两个字符串:主字符串
S
和模式串P
。 - 字符串中的字符限于大小写英文字母和阿拉伯数字。
- 模式串
P
在主字符串S
中出现多次。 - 任务是找出模式串
P
在主字符串S
中所有出现位置的起始下标。
输入格式
- 第一行:输入一个整数
N(1<= N <= 100000)
,表示模式串P
的长度。 - 第二行:输入字符串
P
。 - 第三行:输入一个整数
M(1 <= M <= 1000000)
,表示主字符串S
的长度。 - 第四行:输入字符串
S
。
输出格式
- 共一行:输出所有模式串
P
在主字符串S
中出现的位置的起始下标(下标从 0 开始计数)。整数之间用空格隔开。
输入样例:
3
aba
5
ababa
输出样例:
0 2
代码:
#include<iostream>
using namespace std;
// 定义常量 N 和 M,分别表示模式串的最大长度和文本串的最大长度
const int N = 100010, M = 1000010;
// 定义字符数组 p 和 s,分别用于存储模式串和文本串
char p[N], s[M];
// 定义整数数组 ne,用于存储模式串的前缀函数(部分匹配表)
int ne[N];
// 定义整数 n 和 m,分别表示模式串和文本串的长度
int n, m;
int main()
{
// 读取模式串 p 的长度和内容
cin >> n >> p + 1;
// 读取文本串 s 的长度和内容
cin >> m >> s + 1;
// 构建模式串 p 的前缀函数(部分匹配表)ne
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++; // 如果当前字符匹配,更新j的位置
ne[i] = j; // 保存j到前缀函数数组中
}
// 在文本串 s 中查找模式串 p 的出现位置
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++; // 如果当前字符匹配,更新j的位置
if(j == n)
{
cout << i - n << " "; // 如果找到模式串的匹配,输出匹配的起始位置
j = ne[j]; // 继续查找下一个匹配
}
}
return 0; // 程序结束
}