KMP 算法及其优化
三道统考真题,考的都是手工模拟:2015 问失配后指针的值,2019 数比较次数,2024 求右滑的最长距离。 教材【复习提示】:「重点掌握 KMP 匹配算法的原理及 next 数组的推理过程,手工求 next 数组可以先计算出部分匹配值表然后变形,或根据公式来求解。了解 nextval 数组的求解方法。」next 与 nextval 的两个教材例和逐项验算收在 速查:KMP 的 next 与 nextval,本页补部分匹配值法、比较次数的数法和真题的下标口径。
机制
4.2.2 原理:部分匹配值
- 前缀:除最后一个字符外,字符串的所有头部子串。
- 后缀:除第一个字符外,字符串的所有尾部子串。
- 部分匹配值(PM):字符串的前缀和后缀的最长相等前后缀长度。
以 'ababa' 为例:'a' 为 0;'ab' 为 0;'aba' 的前缀 'abab' 为 2(ab);'ababa' 的交集为 'ababa' 的部分匹配值为 00123。
失配时,已匹配的那段字符就是模式串的某个前缀。若这段前缀的首尾有重合(相等的前后缀),就把模式串右滑到首尾对齐的位置,主串指针 'ababcabcacbab',模式串 'abcac',PM 为 00010。第一趟在第 3 个字符失配,已匹配 2 个,最后一个匹配字符的 PM 为 0,右滑 'abca'),PM 为 1,右滑
从 PM 表到 next 数组
实际匹配时模式串不会滑动,变化的是指针。定义 next[j]:模式串的第 next[j] 位置继续比较。第 next 数组。 右移后第一位空缺用 0 填充(第一个字符失配时,主串指针和模式串指针同步右移一位);最后一个元素的 PM 值溢出,舍去。next[1]=0、next[2]=1 固定不变。
教材注意框:「上述 KMP 算法的举例中,都假设串的编号是从 1 开始的;若串的编号是从 0 开始的,则 next 数组需要整体减 1。」
*next 的递推(标 *)
设 next[j]=k,求 next[j+1]:
- 若
,则 next[j+1]=k+1,即next[j+1]=next[j]+1。 - 若
,令 继续比较 与 ,直到相等( next[j+1]=k+1),或( next[j+1]=1)。
教材图 4.4:模式串 'abaabcaba' 已求得前 6 个 next 值为 0,1,1,2,2,3。求 next[7]:next[6]=3,next[1]=0,所以 next[7]=1。之后 next[8]=2,next[9]=3。
KMP 匹配算法
与简单模式匹配形式相似,不同之处仅在于失配时指针 next[j];且当
- 时间复杂度
;普通模式匹配是 ,但一般情况下实际执行时间近似 ,因此至今仍被采用。 - KMP 仅在主串与子串有很多「部分匹配」时才显得比普通算法快,其主要优点是主串不回溯。
4.2.3 nextval:进一步优化(2024 命题追踪)
不应该出现 next[j] 修正为 next[next[j]],直至两者不相等为止,修正后的数组命名为 nextval。匹配算法不变。
手算模板
① 求 next:先写 PM,右移一位,整体加 1。 例:'ababaaababaa'(4.2.4 第 6 题)的 PM 为 0,0,1,2,3,1,1,2,3,4,5,6,next 为 0,1,1,2,3,4,2,2,3,4,5,6。
② 数比较次数:每一趟从起点比到失配(失配的那次也算)或比到模式串末尾。
③ 右滑距离 nextval 时为
口径差异:真题的下标口径不固定,王道按题目「灵活应变」
教材正文以位序从 1、
next[1]=0为准,但三道统考真题的王道解析都改用了位序从 0、next[0]=-1:2015 的题干写「时 」,解析第一句就是「可知题中的主串和模式串的位序都是从 0 开始的(要注意灵活应变)」;2019、2024 的解析也写「假设位序从 0 开始」。 4.2.4 第 7 题的四个选项干脆都以 开头,答案解析的结论是「 next数组是否整体加 1 都正确,需根据题意具体分析」。 做法:先看题干给出的下标(或选项的首项)定口径,再写next。比较次数和右滑距离不受口径影响,可以放心按自己熟悉的口径算。 这一条修正了 速查表开头「按算竞口径写整张表作废」的说法:只有题目明确要求写出next数组时,口径才决定对错。
边界
| 说法 | 判断 | 说明 |
|---|---|---|
| 「部分匹配值是前缀和后缀的公共元素个数」 | ❌ | 是最长相等前后缀的长度。'ababa' 的公共元素有 |
| 「前缀包括整个字符串」 | ❌ | 前缀是除最后一个字符外的头部子串 |
「next[j]=PM[j]+1」 | ❌ | 是 |
| 「右滑位数 = 已匹配的字符数」 | ❌ | 还要减去对应的部分匹配值 |
| 「KMP 中主串指针可能变小」 | ❌ | 不会变小,主串不回溯 |
「失配时 i=i+1」 | ❌ | |
| 「KMP 在任何情况下都比简单匹配快得多」 | ❌ | 只在部分匹配多时才明显快 |
「nextval 需要重新设计匹配算法」 | ❌ | 匹配算法不变 |
「408 真题的 next 一律从 1 开始」 | ❌ | 2015、2019、2024 的王道解析都按位序从 0 算 |
| 「比较次数只数相等的比较」 | ❌ | 失配的比较也算一次 |
错题复盘:2015 第一次失配后
和 的值
'abaabaabacacaabaabcc','abaabc',第一次失配时(位序从 0 起),下次开始匹配时 , (选 C)。 的 next(从 0 起)为。失配时主串指针不动, 退到 next[5]=2。 选项 B()和 D( )分别错在「 回到开头」和「 后移一位」,这正是简单匹配和 KMP 的两处区别。
错题复盘:2019 数比较次数
主串
'abaabaabcabaabc',模式串'abaabc',匹配成功为止共比较 10 次(选 B)。 第一趟连续比较 6 次,在模式串的 5 号位和主串的 5 号位失配(位序从 0 起);模式串下一个比较位置为next[5]=2,第二趟从模式串 2 号位与主串 5 号位比起,到模式串 5 号位与主串 8 号位匹配,比较 4 次。。 若按简单模式匹配数(失配后主串回溯、模式串从头比),共比较 15 次,正是干扰项 D(已用程序复核)。
错题复盘:2024 修正后的
next数组,右滑的最长距离模式串
'aabaab',使用nextval时,右滑的最长距离是 5(选 A)。 位序从 0 起:next为, nextval为;右滑距离 依次为 ,最大值 5 出现在 。 处 a,next[4]=1,a与之相等;再退到next[1]=0,a仍相等,最终nextval[4]=-1。nextval可能连退两级,右滑距离因此比用next时更大。
错题复盘:
next与nextval的比较次数(4.2.4 第 8、9 题)主串
'aabaaaba',模式串'aaab'。用next(0,1,2,3)共比较次;用 nextval(0,0,0,3)共比较次。 第一趟在 b、a处失配。next让、 依次再跟 比,两次都必然失败; nextval[3]=0直接跳过这两次。这就是 4.2.3 优化的全部内容。
考点
- 部分匹配值的定义;右滑位数 = 已匹配字符数 − 部分匹配值。
next= PM 右移一位再加 1;next[1]=0、next[2]=1。- 失配时
不变、 ; 时同时加 1(2015)。 - 比较次数(2019)与右滑距离(2024)的手工模拟。
nextval的修正规则;匹配算法不变。- 真题下标口径不固定,先看题干。
链接
- 🏠 返回总览:数据结构第 1~4 章总览
- ⬅️ 上一节:4.1 + 4.2.1 串的定义与简单的模式匹配
- ➡️ 下一章:第 5 章 树与二叉树总览
- 🔗 两个教材例、逐项验算与匹配代码:速查:KMP 的 next 与 nextval
- 🔗 KMP 代码板子:板子:KMP
- 📖 名词库:第 1~4 章名词库