“单模式匹配中,KMP 算法用前缀函数记住“失配之后还能保留多长的已匹配前缀”;Aho–Corasick 把这一思想搬到模式集合上:Trie 同时记住所有模式的所有前缀,失败链接则回答“当前候…”
形式陈述 ​
给定文本
q = 0
for i = 0, 1, ..., |T| - 1:
while q > 0 and T[i] != P[q]:
q = π[q - 1]
if T[i] == P[q]:
q = q + 1
if q == m:
report i - m + 1
q = π[m - 1]
最后一行回退而不是清零,使重叠出现也能被报告。预处理耗时 P[q] 访问。
直觉
朴素匹配在失配后把模式起点右移,再从模式首字符重读文本。KMP 保留了已经证明的信息:若当前匹配了
文本下标 while 迭代都使它严格下降。整个扫描中的下降总量受上升总量限制,因此字符比较次数是线性的,而不是每个文本位置都乘上模式长度。
这个状态还给出正确性不变量。在处理
例子与边界
取模式 ababaca,其前缀函数是 abababaca 中扫描。读完前五个字符 ababa 后有 b,与模式位置 c 失配,于是回退到
已读文本的后缀 aba 正好也是模式前缀;仍用当前这个 b 与 a、c、a,最终在文本位置 aba 没有被重新读取,只被重新解释为较短的有效前缀状态。
重叠匹配说明了报告后的回退为何必要。模式 aaa 在文本 aaaaa 中出现在位置
KMP 解决的是单模式精确匹配。字符相等关系必须稳定,文本流中的每个符号一旦处理便不再回看;若问题允许编辑距离、通配符或完整正则语义,状态需要同时表达更多可能位置,不能只把 == 换成一个宽松谓词。多个模式共享扫描时,Trie 与 Aho–Corasick 自动机会把许多模式前缀合并;这不是把一条 KMP 失败链简单复制多次。
在字符访问模型中,算法至少必须读取所有可能影响答案的文本位置,并读取模式本身,因此
推论与应用
KMP 的扫描状态只依赖当前
字符串匹配定义输入与输出,前缀函数提供模式内部的回退结构,KMP 则负责把它变成文本扫描器。周期、border 和前缀出现次数属于前缀函数本身的结构应用;在文本中逐个报告模式出现位置,才是 KMP 的核心职责。
算法选择取决于被预处理的对象。KMP 预处理一个模式并在线读取任意文本;后缀树和 FM-index预处理固定全文,以支持许多模式查询,后者还追求压缩空间。把全文索引称为“更快的 KMP”会混淆在线单模式匹配、静态多查询和压缩表示这三个不同目标。
参考资料
- Donald E. Knuth, James H. Morris Jr., and Vaughan R. Pratt, “Fast Pattern Matching in Strings,” SIAM Journal on Computing 6(2), 1977, pp. 323–350.
- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein, Introduction to Algorithms, 4th ed., MIT Press, 2022, Ch. 32.