1、KMP概念
KMP(Knuth-Morris-Pratt)算法的核心在于 利用模式串自身的重复性,通过 预处理 next
数组 来优化匹配过程,避免主串回溯,从而提升匹配效率。其关键点可总结如下:
- 主串不回溯:传统暴力匹配在失配时主串和模式串都要回溯,而 KMP 仅调整模式串的位置,主串指针
i
始终向前移动,避免不必要的重复匹配。 - 利用
next
数组:next[j]
表示模式串p[0..j]
的 最长相等前后缀长度,用于在失配时快速调整模式串的匹配位置。
2、实战项目
给定主串s,字串p,求解字串在主串的位置索引。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10; // 定义足够大的数组大小
char p[N], s[N]; // p是子串,s是主串(从索引0开始存储)
int ne[N]; // next数组(部分匹配表)
int main() {
int n, m;
cin >> n >> p >> m >> s;
ne[0] = -1;
for (int i = 1, j = -1; i < n; i ++) // 计算next数组(KMP预处理阶段)
{
while (j != -1 && p[i] != p[j + 1]) j = ne[j]; // 且当前字符不匹配时,回退j到ne[j]
if (p[i] == p[j + 1]) j++; // 如果当前字符匹配,j向后移动
ne[i] = j; // 记录当前位置的最长相等前后缀长度
}
// KMP匹配阶段(在主串s中查找模式串p)
for (int i = 0, j = -1; i < m; i++)
{
while (j != -1 && s[i] != p[j + 1]) j = ne[j]; //且当前字符不匹配时,回退j到ne[j]
// 如果当前字符匹配,j向后移动
if (s[i] == p[j + 1]) j++;
if (j == n - 1)
{
cout << i - j << ' ';
j = ne[j]; // 回退j,继续寻找
}
}
return 0;
}
3、难点
难点就是求解next数组,也就是匹配表。
以abcab为例:
所有前缀组合:a, ab, abc, abca;
所有后缀组合:b, ab, cab, bcab;
可以发现前缀规律:每一个前缀的开头一定是a字符;也就是在匹配表中,如果回溯1步,那么一定是回溯到a的位置;类似的,2步应该到ab位置,3步应该到abc位置。如果当前ne[j] = 2(0,1,2匹配),那么前面两个数也是可以回溯的,也就是前面j-1, j-2索引上的数也是匹配上了的。