KMP 算法
含AI生成内容
KMP 算法
核心思想
KMP(Knuth-Morris-Pratt)算法是 串 的模式匹配优化算法。其核心思想是:利用已匹配信息确定模式串的滑动位置,主串指针永不回退。
BF算法(朴素模式匹配) 每次失配都要将主串指针回退到起点的下一位,大量比较被浪费。KMP 的洞察是:失配时,模式串中已匹配的部分包含了足够的信息来决定下一步从哪里继续比较。通过预先计算的 next 数组,模式串可以跳过那些不可能匹配的位置,而主串指针始终向前移动。
BF 算法(失配时): KMP 算法(失配时):主串 i 回溯 主串 i 不动模式串 j 归零 模式串 j = next[j],向右滑动KMP 算法的时间复杂度为 O(n+m),远优于 BF 的 O(nm)。
next 数组(考研必考)
next 数组的含义
next[j] 表示:当模式串第 j 个字符与主串失配时,模式串应回退到第 next[j] 个字符继续比较。
手工求解步骤
对模式串 T[1..j-1](即第 j 个字符之前的子串),找其最长相等前后缀的长度 k,则 next[j] = k + 1。
特别规定:next[1] = 0(第 1 个字符就失配,表示主串 i 要后移,模式串从头开始)。
手工求解口诀:看第 j 个字符前面的子串,找最长相等前后缀长度,加 1 就是 next[j]。
求解示例
以模式串 T = "abaabcac"(下标从 1 开始)为例:
| j | 子串 T[1..j-1] | 最长相等前后缀 | 长度 k | next[j] = k+1 |
|---|---|---|---|---|
| 1 | (空) | — | — | 0(规定) |
| 2 | ”a” | 无 | 0 | 1 |
| 3 | ”ab” | 无 | 0 | 1 |
| 4 | ”aba" | "a” | 1 | 2 |
| 5 | ”abaa" | "a” | 1 | 2 |
| 6 | ”abaab" | "ab” | 2 | 3 |
| 7 | ”abaabc” | 无 | 0 | 1 |
| 8 | ”abaabca" | "a” | 1 | 2 |
最终结果:next[] = {0, 1, 1, 2, 2, 3, 1, 2}
两种定义的辨析
| 定义方式 | next[1] | 适用教材 | 影响 |
|---|---|---|---|
| 下标从 1 开始,next[1] = 0 | 0 | 严蔚敏版《数据结构》(408 教材) | 考研默认使用 |
| 下标从 0 开始,next[0] = -1 | -1 | 部分程序设计教材 | 代码实现不同 |
做题时务必先看清楚题目用的是哪种定义,否则整个数组会错位。
代码实现
// 求 next 数组(串下标从 1 开始)void getNext(char T[], int next[], int m) { int j = 1, k = 0; next[1] = 0; while (j < m) { if (k == 0 || T[j] == T[k]) { j++; k++; next[j] = k; } else { k = next[k]; // k 回退(核心步骤) } }}代码要点:
k == 0表示已退无可退,next[j+1] = 1T[j] == T[k]表示前后缀可以扩展一位,next[j+1] = k+1k = next[k]是利用已有 next 值进行回退,这一步骤的思想和 KMP 匹配过程完全一致
KMP 匹配过程
int KMP(char S[], char T[], int next[], int n, int m) { int i = 1, j = 1; while (i <= n && j <= m) { if (j == 0 || S[i] == T[j]) { i++; j++; } else { j = next[j]; // 主串 i 不回溯,模式串 j 回退 } } if (j > m) return i - m; return -1;}匹配过程关键点:
j == 0时说明模式串第 1 个字符就失配,此时i++, j++(主串后移一位,模式串从头开始)S[i] == T[j]时两个指针同时后移- 失配时
j = next[j],模式串右滑,跳过不可能匹配的位置
nextval 优化
为什么需要 nextval
next 数组存在一个问题:如果 T[j] == T[next[j]],那么回退到 next[j] 后必然还是失配(因为和同一个字符比较),造成多余比较。
手工求 nextval(考研常考)
先求出 next 数组,再逐个修正:对每个 j,若 T[j] == T[next[j]],则 nextval[j] = nextval[next[j]];否则 nextval[j] = next[j]。
以 T = "abaabcac" 为例:
| j | T[j] | next[j] | T[next[j]] | 是否相等 | nextval[j] |
|---|---|---|---|---|---|
| 1 | a | 0 | — | — | 0 |
| 2 | b | 1 | a | b != a | 1 |
| 3 | a | 1 | a | a == a | 0(取 nextval[1]) |
| 4 | a | 2 | b | a != b | 2 |
| 5 | b | 2 | b | b == b | 1(取 nextval[2]) |
| 6 | c | 3 | a | c != a | 3 |
| 7 | a | 1 | a | a == a | 0(取 nextval[1]) |
| 8 | c | 2 | b | c != b | 2 |
最终结果:nextval[] = {0, 1, 0, 2, 1, 3, 0, 2}
代码实现
void getNextval(char T[], int nextval[], int m) { int j = 1, k = 0; nextval[1] = 0; while (j < m) { if (k == 0 || T[j] == T[k]) { j++; k++; if (T[j] != T[k]) nextval[j] = k; else nextval[j] = nextval[k]; // 相等则继续回退 } else { k = nextval[k]; } }}复杂度分析
| 阶段 | 时间复杂度 | 说明 |
|---|---|---|
| 求 next 数组 | O(m) | m 为模式串长度,指针不回溯 |
| KMP 匹配 | O(n) | n 为主串长度,主串指针不回溯 |
| 总体 | O(n+m) | 远优于 BF 的 O(n x m) |
空间复杂度:O(m),需要长度为 m 的 next(或 nextval)数组。
BF 算法 vs KMP 算法对比
| 对比项 | BF 算法 | KMP 算法 |
|---|---|---|
| 核心策略 | 穷举所有对齐位置 | 利用已匹配信息滑动 |
| 主串指针 | 失配时回退 | 永不回退 |
| 模式串指针 | 失配时归零 | 失配时 j = next[j] |
| 预处理 | 无 | 需求 next 数组 O(m) |
| 最好时间复杂度 | O(n+m) | O(n+m) |
| 最坏时间复杂度 | O(nm) | O(n+m) |
| 空间复杂度 | O(1) | O(m) |
| 适用场景 | 短模式串、简单匹配 | 长模式串、重复字符多 |
考研高频考点
- 手工求 next 数组(选择题/填空题每年必考,务必熟练)
- 手工求 nextval 数组(在 next 基础上修正,常与 next 一起考)
- KMP 算法的时间复杂度 O(n+m) 及其与 BF 算法 O(nm) 的对比
- next 数组的含义:最长相等前后缀长度 + 1(概念题)
- KMP 主串指针不回溯的特性(简答题/判断题)
- 给定 next 数组模拟 KMP 匹配过程(手动模拟题)
- next 数组求解代码中
k = next[k]的含义(代码分析题) - next 数组两种定义的辨析(下标从 0 开始 vs 从 1 开始)
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!










