当前位置: 首页 > news >正文

KMP 算法

在字符串匹配问题中,通常面临这样的任务:给定一个文本串 \(S_1\) 和一个模式串 \(S_2\),找出 \(S_2\)\(S_1\) 中出现的位置。

最直观的方法是暴力匹配:从 \(S_1\) 的第一个字符开始,逐个比较 \(S_2\);如果匹配失败,\(S_1\) 的指针向后移动一位,\(S_2\) 的指针回到开头重新匹配。这种方法的时间复杂度最坏是 \(O(|S_1| \times |S_2|)\),如果字符串比较随机,暴力匹配的效率是不低的,但可以构造测试数据,使暴力匹配的做法超时。暴力匹配效率低是因为比对失败时还需要从头开始匹配,在失配(匹配失败)之后,如果可以利用已经匹配过的信息,将 \(S_2\) 尽可能多地向右“滑动”一段距离,就可以提升效率,这就是 KMP(Knuth-Morris-Pratt)算法的优化思路。

image

例如,文本串是“abcacababcab”,模式串是“abcab”,首先将文本串和模式串放在一起进行匹配,发现文本串第 4 个字符“c”(这里定义字符串开头是第 0 个字符)和模式串第 4 个字符“b”失配。这时将模式串向右移动 3 位,使移动后的第 0 位和移动前的第 3 位对齐,然后继续判断匹配。同理,对于另外一个例子:当模式串第 5 位失配时,可以发现第 0 位到第 2 位的子串和第 2 位到第 4 位的子串是是一样的,将模式串往后移动两位,然后继续尝试匹配。

像这样,对于模式串的每一位,都存在唯一的“特定变化位数”:在这一位失配时,可以直接将模式串往右移动这一“特定变化位数”。可以发现,对于左边的例子来说,模式串第 4 位失配时,考察第 0 位到第 3 位这个子串,它的前缀“a”和后缀“a”是相同的;对于右边的例子来说,模式串第 5 位失配时,考察第 0 位到第 5 位这个子串,它的前缀“aba”和后缀“aba”是相同的。像这样前缀和后缀相同的情况下,在失配前,模式串的后缀已经验证了可以和文本串匹配,那么这个前缀也是可以匹配的,不需要重复枚举匹配。这对相同的前缀和后缀,通常称为 border。

如何获得模式串的每个前缀 \(S'\) 的最长 border \(T'\) 的长度呢?朴素枚举的做法是针对每个模式串的每一个前缀,依次枚举它的前缀和后缀长度,然后比较这两个子串是否相同。这种做法的时间复杂度很高,因此需要进一步优化。

image

令模式串第 0 位到第 \(i\) 位的子串的最长 border 的长度为 \(b_i\),将其称为前缀函数。可以发现,在已经得到 \(b_{i-1}\) 的情况下,可以递推出 \(b_i\) 的结果。如上图,已知 \(b_{i-1}=3\),计算 \(b_i\) 的时候,如果发现 \(S_3\) 等于 \(S_i\),则可以借助之前已经记录的信息,得到 \(b_i = b_{i-1}+1\) 的结论。可以利用反证法证明:\(b_i\) 最多比 \(b_{i-1}\)\(1\),不可能多更多。如果 \(S_3 \ne S_i\),则继续尝试枚举更短的前后缀并判断是否相同,然后继续尝试匹配。考察枚举更短前后缀的循环,如果需要缩减一次前后缀长度,则必须有对应的增加前后缀的长度的过程,而增加的次数不会超过 \(|S|\),所以减少长度的次数也不会超过 \(|S|\)。因此,这种做法的时间复杂度是 \(O(|S|^2)\)

image

实际上,找到更短 border 的过程也可以优化。如上图所示,\(b_{i-1}=4\),发现 \(S_4 \ne S_i\),因此需要找 \(S_{0 \dots i-1}\) 子串中次短的 border。由于 \(S_{0 \dots 3} = S_{i-4 \dots i-1}\),所以计算 \(S_{0 \dots i-1}\) 子串中次短的 border 长度就是 \(S_{0 \dots b_{i-1}-1}\) 的最长 border 长度,即 \(b_{b_{i-1}}\)。如果发现还是不能匹配,则可以继续迭代,继续缩短长度。这种做法不需要判断子串是否相同,时间复杂度是 \(O(|S|)\)

于是,如何借助前缀函数进行字符串匹配的做法就显而易见了:首先将模式串和文本串的开头对齐,然后比较文本串和模式串的第 0 位是否一致,如果一致则继续匹配下一位;如果不匹配则将模式串往右移动,使它的左 border 和它原来的右 border 重合,这种算法就是 KMP 算法。代码实现中,可以将模式串的指针设为 \(j\),将文本串的指针设为 \(i\),如果失配,则将 \(j\) 的值设置为 \(b_{j-1}\);当匹配完所有的模式串字符,说明找到了一个匹配,根据 \(i\) 的位置推导出匹配到的位置。

例题:P3375 【模板】KMP

参考代码
#include <cstdio>
#include <cstring>const int N = 1e6 + 5;
char s1[N], s2[N];
// b[i] 表示 s2 中以 i 结尾的前缀子串的最长相等前后缀长度 (border 长度)
int b[N];int main()
{// 读取两个字符串,s1 是文本串,s2 是模式串scanf("%s%s", s1, s2);int n = strlen(s1), m = strlen(s2);// --- 计算 b 数组 (前缀函数的计算) ---// b[0] 必然为 0,因为 border 不能是本身b[0] = 0;// i 从 1 开始遍历 s2,j 表示当前匹配的最长前缀长度 (也是前一个位置的 border 长度)for (int i = 1, j = 0; i < m; i++) {// 如果当前字符 s2[i] 不匹配 s2[j] (j 同时也是下一个待匹配字符的下标),// 则回退 j 到上一个 border 的长度,直到匹配或 j 归零while (j > 0 && s2[i] != s2[j]) j = b[j - 1];// 如果匹配,最长前缀长度 +1if (s2[i] == s2[j]) j++;// 记录 s2[0...i] 的最长 border 长度b[i] = j;}// --- KMP 匹配过程 ---// i 遍历文本串 s1,j 表示当前 s2 已匹配的长度for (int i = 0, j = 0; i < n; i++) {// 如果 s1 当前字符与 s2 下一个字符不匹配,回退 jwhile (j > 0 && s1[i] != s2[j]) j = b[j - 1];// 如果匹配,s2 的匹配长度 +1if (s1[i] == s2[j]) j++;// 如果 j 等于 m,说明 s2 已经完全匹配if (j == m) {// 输出匹配位置 (题目要求 1-based index)// i 是结束位置 (0-based),起始位置是 i - m + 1// 转换为 1-based 需要 +1,即 i - m + 2printf("%d\n", i - m + 2);// 继续寻找下一次匹配,利用 border 性质回退// 这里不能重置 j=0,因为可能存在重叠匹配j = b[j - 1];}}// --- 输出 next 数组 ---// 题目要求输出 s2 每个前缀的最长 border 长度for (int i = 0; i < m; i++) printf("%d ", b[i]);return 0;
}
http://www.jsqmd.com/news/289456/

相关文章:

  • 详细介绍:跨端一致性与体验统一:构建面向全场景的 Flutter UI 自适应架构
  • 代码中接收命令行参数,通过jenkins部署时传入不同的环境命令行参数--针对代码在不同环境下运行
  • 衡阳国家高新技术产业开发衡山科学城英语雅思培训辅导机构推荐;2026权威出国雅思课程中心学校口碑排行榜
  • P3781 [SDOI2017] 切树游戏
  • 2026年苏州门窗厂家深度选型指南:如何为你的装修需求匹配最佳方案?
  • Google Gemini系列:多模态AI的迭代演进与前沿应用
  • 邵阳双清大祥北塔邵东武冈英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜
  • 100V8A_HN0801雾化器加湿器MOS管关键特性
  • Java毕设项目:基于springboot的家庭物品收纳管理系统(源码+文档,讲解、调试运行,定制等)
  • 【计算机毕业设计案例】基于springboot个性化服务智能提醒的社区老年康养管理系统(程序+文档+讲解+定制)
  • Java毕设项目:基于springboot的交通安全知识学习平台(源码+文档,讲解、调试运行,定制等)
  • 【计算机毕业设计案例】基于springboot的家庭物品收纳管理系统基于Springboot+Vue的个人物品管理系统(程序+文档+讲解+定制)
  • 基于智慧本体条款的先进AI模型模拟裁决分析 / Simulated Adj. Analysis of Adv. AI Models Based on Wisdom Ontology Clauses
  • Java计算机毕设之基于Springboot+Vue的个人物品管理系统基于springboot的家庭物品收纳管理系统(完整前后端代码+说明文档+LW,调试定制等)
  • 【毕业设计】基于springboot的交通安全知识学习平台(源码+文档+远程调试,全bao定制等)
  • 广州研究生留学机构如何选?top10稳定可靠机构推荐。
  • 【课程设计/毕业设计】基于springboot的生活物品收纳管理系统的设计家庭物品收纳管理系统【附源码、数据库、万字文档】
  • 【无人机编队】基于方位测量的四旋翼无人机主从编队跟踪控制附matlab代码
  • 权威评测!宁波研究生留学中介前十名排名,值得信赖机构深度解析
  • 深圳研究生留学中介top10,录取率高,助你顺利实现留学目标
  • 苏州研究生留学中介top10大揭秘:性价比高,助你顺利出国
  • 天津地区top10硕士留学中介盘点,承诺无隐形消费,值得信赖
  • 香港top10研究生留学中介,录取案例多,如何挑选?详细指南
  • 新加坡留学中介top10推荐:申请成功率高,助力留学成功
  • 郑州硕士留学中介口碑排名发布,申请成功率高机构盘点
  • 2026年知名的矿山监理_矿山施工_矿山设计_环境监理公司真实口碑排行榜
  • Java计算机毕设之基于springboot个性化智能提醒的社区老年康养管理系统智能药物提醒和管理(完整前后端代码+说明文档+LW,调试定制等)
  • 详细介绍:Visual Studio 2026 现已正式发布,更快、更智能!
  • 【毕业设计】基于springboot个性化智能提醒的社区老年康养管理系统(源码+文档+远程调试,全bao定制等)
  • 【毕业设计】基于springboot的家庭物品收纳管理系统(源码+文档+远程调试,全bao定制等)