引言
b站上看到个kmp讲解,讲解得非常清晰,特此做笔记记录下。
KMP (Knuth-Morris-Pratt)算法是一种用于在文本串(主串)中查找子串的高效算法,特别适用于需要频繁查找多个子串的情况。
它的核心思想是利用已经部分匹配的信息,避免在主串中重复检查已经匹配过的字符序列,从而达到减少匹配次数和提高匹配效率的目的。
前缀函数
记π[i] = 第i个前缀的最长匹配真前后缀的长度
π[0] = 0
示例:
s = ATAATA
| 下标 i | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 字符串 s[i] | A | T | A | A | T | A |
| 前缀 s[0…i] | A | AT | ATA | ATAA | ATAAT | ATAATA |
| π[i](前缀函数值) | 0 | 0 | 1 | 1 | 2 | 3 |
前缀函数应用
将搜索词称作模式串 s
将正文称作主串 S
思路
为了利用前缀函数(π 数组)高效匹配,我们可以将两者拼接成一个新串:
s + '#' + S
(其中 # 是一个不会在两串中出现的分隔符)
计算这个新串的前缀函数数组 π,
当某个位置 i 满足:
π[i] == s.length()
就说明在主串中,出现了一个与模式串完全匹配的子串。
示例
在主串 AABAATAATA 中查找模式串 ATAATA
模式串 s = "ATAATA"
主串 S = "AABAATAATA"
组合串 str = "ATAATA#AABAATAATA"
步骤 1:计算组合串的 π 数组
| 下标 i | 字符 | 前缀 | π[i] |
|---|---|---|---|
| 0 | A | A | 0 |
| 1 | T | AT | 0 |
| 2 | A | ATA | 1 |
| 3 | A | ATAA | 1 |
| 4 | T | ATAAT | 2 |
| 5 | A | ATAATA | 3 |
| 6 | # | ATAATA# | 0 |
| 7 | A | ATAATA#A | 1 |
| 8 | A | ATAATA#AA | 1 |
| 9 | B | ATAATA#AAB | 0 |
| 10 | A | ATAATA#AABA | 1 |
| 11 | A | ATAATA#AABAA | 1 |
| 12 | T | ATAATA#AABAAT | 2 |
| 13 | A | ATAATA#AABAATA | 3 |
| 14 | A | ATAATA#AABAATAA | 1 |
| 15 | T | ATAATA#AABAATAAT | 2 |
| 16 | A | ATAATA#AABAATAATA | 6 ✅ |
步骤 2:匹配结果
- 当
π[16] == 6 == s.length()时,说明匹配成功。 - 位置计算:
匹配子串起始下标 = 当前下标(16) − 模式串长度(6) − 分隔符(1) − 模式串长度(6) + 1
→ 在主串S的第 4 个字符开始匹配。
即:
A A B A A T A A T A
↑
从这里开始出现 ATAATA
讨论如何快速求解π[i]
//创建 π 数组,π[i] 表示前缀 s[0..i] 的最长匹配真前后缀长度。
vector<int> pi(str.size());
for(int i = 1; i < str.size(); i++>){
// 求解 pi[i] 时 我们可以先想 pi[i-1] 的情况。从上一个位置的最长匹配长度开始尝试。
int len = pi[i - 1];
//如果当前字符与期望位置不匹配,就回退到更短的“可用前缀”,核心所在
while(len != 0 && str[i] != str[len]){
len = pi[len - 1];
}
// 如果匹配 字符扩展一位
if(str[i] == str[len]){
pi[i] = len + 1;
}
}
对于每个位置 i,我们尝试在上一个位置 i-1 的最长匹配 pi[i-1] 的基础上延长一个字符;如果延长失败,就利用 pi 数组中的信息不断跳到“较短但仍然是前后缀”的长度上再尝试,直到成功或退到 0。这样避免了重复比较,使整体为线性时间。
时间复杂度分析
虽然看起来 while 可能反复回退,但注意:
-
每次循环中 len 都会减小;
-
而匹配成功时 len 增大;
-
整个过程中 len 增加与减少的总次数 ≤ n。
因此,整体时间复杂度是 O(n),远优于暴力匹配的 O(n²)。
总结
KMP 的核心思想就是“匹配失败时不回退主串指针”,
利用前缀函数 π 数组快速跳转到下一个可匹配的前缀位置。
这样就能保证每个字符只被访问有限次,实现线性匹配。

被折叠的 条评论
为什么被折叠?



