KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,其核心思想是避免在主串中回溯指针,通过预处理模式串生成 next 数组来指导匹配失败时模式串的滑动位置。匹配过程中:
- 初始化两个指针:
i指向主串 S 的当前字符,j指向模式串 P 的当前字符(初始均为 0)。 - 当
S[i] == P[j]时,双方指针同时后移继续比较。 - 当
S[i] != P[j]时,不移动主串指针i,而是将模式串指针j更新为next[j],即j = next[j],表示利用已知的最长相等前后缀长度跳过不必要的比较。 - 若
j == -1(或某些实现中为 0),说明模式串已退回到起始位置,此时将i和j都加 1,进入下一轮匹配。 - 一旦
j达到模式串长度,则匹配成功,返回匹配起始位置。
其中,next[j] 表示模式串中前 j 个字符构成的子串的最长相等真前后缀的长度。该数组可在 O(m) 时间内预处理得到。
数组是数据结构中的基本多维存储结构,其特点如下:
- 定长性:创建时确定维度和大小,运行期间不可更改。
- 同构性:所有元素类型相同,存储结构一致。
- 线性扩展:一维数组是线性表;二维数组可看作“其元素也为线性表”的线性表,例如 A[m][n] 可视为 m 个长度为 n 的行向量组成的线性表。
- 随机访问:通过下标可在 O(1) 时间内计算地址访问元素(按行/列优先映射到一维内存)。
矩阵常以二维数组表示,而广义表则是更灵活的递归结构,允许元素为原子或子表,是树与图等非线性结构的基础抽象。
KMP 算法提升了朴素匹配的效率(从 O(nm) 到 O(n+m)),而数组作为基础结构支撑了矩阵压缩(如稀疏矩阵的三元组表示)、广义表的存储实现等高级主题。
构造 KMP 算法中的 next 数组,本质是对模式串本身进行“自我匹配”,求出每个位置前缀的最长相等真前后缀(即:最长的、不等于自身的前缀与后缀相等的部分)的长度。next[j] 表示当模式串 P[j] 匹配失败时,新的匹配起点位置。
✅ 构造 next 数组的具体步骤
设模式串为 P,长度为 m,next[0] = -1 或 0(取决于实现方式),我们采用常见的一种:
next[j]表示子串P[0..j-1]的最长相等真前后缀的长度。- 使用双指针法构造:
def compute_next(P):
m = len(P)
next = [0] * m # 初始化 next 数组
next[0] = -1 # 第一个字符无前缀,设为 -1
j, k = 0, -1 # j: 当前位置;k: 上一次最长前后缀长度(也作匹配指针)
while j < m - 1:
if k == -1 or P[j] == P[k]:
j += 1
k += 1
next[j] = k # 若 P[j]==P[k],则 next[j+1] = k+1
else:
k = next[k] # 不匹配则回退 k
return next
📘 示例:模式串 P = "ababaa"
我们要为 "ababaa" 构造 next 数组。
| j(下标) | 字符 | 解释说明 | next[j] |
|---|---|---|---|
| 0 | a | 首字符,定义为 -1 | -1 |
| 1 | b | 子串 “a”:无公共前后缀 → 0 | 0 |
| 2 | a | 子串 “ab”:前缀{a}, 后缀{b} → 无公共 → 0 | 0 |
| 3 | b | 子串 “aba”:前缀 a, ab;后缀 a, ba → 最长公共为 “a” → 长度 1 | 1 |
| 4 | a | 子串 “abab”:前缀 a,ab,aba;后缀 bab,ab,b → 公共 “ab” → 长度 2 | 2 |
| 5 | a | 子串 “ababa”:前缀中 a,aba;后缀中 aba,a → 最长 “aba” → 长度 3 | 3 |
所以最终 next = [-1, 0, 0, 1, 2, 3]
注意:有些教材将
next[0]设为 0 并整体偏移,但逻辑一致。
🔍 next 数组的作用直观理解
例如在匹配过程中,若 P[5]='a' 失败,j = next[5] = 3,意味着我们可以跳到 P[3] 继续比较,因为前面已有长度为 3 的公共前后缀 "aba",无需重新从头开始。
💡 小结
next数组基于模式串自身结构构建。- 核心思想:利用已知的“最长相等前后缀”避免无效匹配。
- 时间复杂度:O(m),空间复杂度:O(1) 辅助变量。


2313

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



