KMP 字符串匹配及基础应用
一些闲话
CSP 考前一个多月,发现自己字符串学的都忘完了,赶紧来开坑。
基本算法(板题 洛谷 P3375)
KMP(Knuth Morris Pratt)字符串匹配算法:在 \(\mathcal O(n+m)\) 的时间内,在原串 \(S\) 内查找为 \(P\) 的子串。
算法流程分为两部分:1. 预处理并计算 \(next\) 数组;2. 利用上一步的结果进行字符串匹配。
下面约定 \(n=|P|, m=|S|\)。
next 数组的定义
对于模式串 \(P\) 中 \(P_{1 \sim i}\) 的前缀,定义 \(next_i\) 为满足 \(P_{1 \sim k} = P_{(i-k+1) \sim i}\) 的最大 \(k(k < i)\) 的值。
即找出 \(P_{1 \sim i}\) 的最长真前缀使得它等于 \(P_{1 \sim i}\) 的一段等长的后缀。
注:\(next\) 数组,国外文献中一般称为 LPS Table,即 Longest Prefix which is also Suffix,可以对照其英文理解。
next 数组有什么用(如何利用 next 来匹配)
假如我们已经求出了 \(next_1 \sim next_n\),考虑利用其进行子串匹配。
对于 \(S\) 中的每一个位置 \(i\),令变量 \(j\) 表示模式串 \(P\) 已经匹配完了 \(j\) 个字符。
其初值表示上次已经匹配到了 \(P_j\) 的位置,即以 \(S_{i-1}\) 结尾的一段长度为 \(j\) 的后缀与 \(P_{1 \sim j}\) 是匹配的。
现在我们检查 \(P_{j+1}\) 是否与 \(S_i\) 匹配。(步骤 A)
-
如果相等,那么 \(S\) 与 \(P\) 又往后多匹配了一位,转步骤 B。
-
如果不等,朴素的想法是将 \(j\) 回到 \(1\) 重新匹配;但由 \(next\) 数组的定义,我们不妨令 \(j=next_j\)(见下图),然后回到步骤 A。
接着让 \(i\) 和 \(j\) 分别 \(+1\),继续匹配下一个字符。(步骤 B)
如果在某一时刻有 \(j=n\),那么说明 \(S_{(i-n+1) \sim i} = P\),匹配成功。令 \(j=next_j\) 继续匹配。
可以证明,时间复杂度为 \(\mathcal O(m)\)。
参考代码(C++):
for (int i = 1, j = 0; i <= m; ++i) {
while (j && S[i] != P[j + 1]) j = nxt[j];
if (S[i] == P[j + 1]) j++;
if (j == n) {
printf("Found pattern! Begins at: %d\n", i - n);
j = nxt[j];
}
}
如何高效地求 next 数组
对于 \(P\) 中的位置 \(i\),假设我们已经求出 \(1 \sim i-1\) 的 \(next\),考虑如何求 \(next_i\)。
同样令变量 \(j\) 初值为 \(next_{i-1}\)。
比较 \(P_i\) 与 \(P_{j+1}\),分两种情况讨论:
-
如果 \(P_i = P_{j+1}\),说明 \(P_{1 \sim i}\) 中长度为 \(j\) 的前缀向后扩展一位后,仍然等于其同样长的后缀。这符合 \(next\) 的定义,因此 \(next_i = j+1\)。
-
否则,令 \(j=next_j\),重新比较(原理见下图)。
同样可以证明,上述操作是 \(\mathcal O(n)\) 的。
参考代码(C++):
// 注意由于 next[i] < i,所以 next[1] = 0,i 从 2 开始
for (int i = 2, j = 0; i <= n; ++i) {
while (j && P[i] != P[j + 1]) j = nxt[j];
if (P[i] == P[j + 1]) j++;
nxt[i] = j;
}
将二者放在一起,即可在 \(\mathcal O(n+m)\) 的时间复杂度内解决子串匹配问题。
至于复杂度证明…… 由于作者不会证这玩意儿,这里就不展开了。
(感性理解一下,这个基于双指针的算法,跳 \(next\) 跳起来是很快的,每次均摊是常数级别?反正是比暴力匹配快很多)
如果你背不出 KMP 板子
这里有一个解决方案
当然是写字符串哈希了,如果你不放心就取多个模数好了~
但是遇到下面的应用,而不是裸的匹配题,字符串哈希就很难有用武之地了,所以建议还是把板子记下来。
(查了一下,利用字符串哈希做子串匹配的方法,在国外居然还有个名字叫 Rabin-Karp 算法?)
循环节问题(板题 洛谷 P4391)
待补充
哦对了,还有一道最长循环节问题,可以留作思考~
带删的字符串匹配(板题 洛谷 P4824)
待补充
Z 函数 / 扩展 KMP(板题 洛谷 P5410)
待补充
一些待完成的习题
更多请参见 OI-wiki
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】