(c6)KMP算法:再改进

官官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·38 天前
字符串匹配next数组nextval边界分析

KMP算法:再改进

看懂next数组为何仍会浪费比较,掌握nextval的推导与边界细节

按 空格/→ 演示下一步

问一句
1 / 10 页

全部页面点击任意一页,跳回舞台从这页播放

字符串匹配next数组nextval边界分析

KMP算法:再改进

看懂next数组为何仍会浪费比较,掌握nextval的推导与边界细节

1第 1 页 · KMP算法:再改进

美中不足:next的盲点

上一页我们用 next 数组加速了匹配,但细想一步:失配时我们只知道「前面有多长的前后缀相等」,却完全没看失配的那个字符长什么样——它可能白跳一步。

next 的盲点
构造 next 只看失配字符之前的前后缀,不含它本身
可能白跳
若跳回位置的字符与失配字符相同,比较必然再失败一次
改进方向
把失配字符纳入比较,直接跳得更远,得到 nextval
导航只看路况不看目的地对应 →next 的盲点

导航只算路长却不看前方是否封路,对应 next 不看失配字符

2第 2 页 · 美中不足:next的盲点

缺陷实例图解

顺着箭头看一次失配后的跳转流程:绿色判断点是关键——跳转目标若与失配字符相同,这次比较注定失败。

图解渲染中…
c1:判断跳转目标字符P2是否等于失配的P5,同字符则白比d1:主串位置没动,P2也是a,而T处刚证明不匹配a,必失败f1:引入nextval:若P[j]=P[next[j]],直接取nextval[next[j]]
3第 3 页 · 缺陷实例图解

以卵击石:无谓的比较

前一页看到:aba cab 失配后跳到下一个 a,还是撞上同一个 c。这不是运气差——跳过去的新字符,很可能和原来失配的那个字符一模一样,白比一次。

跳后同字符
next[j] 指向的新字符与失配字符相同时,比较必然失败
无谓的比较
明知会失配还是比较,浪费一次操作
改进方向
连续相同就继续回跳,让指针一次跳到位
换人接球仍漏对应 →next多次回跳

换一个人还是接不住同一个球,就该直接换到接得住的人

4第 4 页 · 以卵击石:无谓的比较

前车之覆:失败从何而来

逐帧回放一次失配,看清比较浪费在哪一帧。

1
主串卡在i
i 不回退,停在失配位置等待新对齐
2
模式跳到j'
按 next 跳转,i 与 j' 重新对齐比较
3
字符仍然相等
P[j'] 与 P[j] 相同,这次比较注定失败
4
再次失配
白比一次才暴露问题,浪费就发生在这一帧
5
定位根因
next 只看前缀,没排除与当前字符相同的情况
6
引出nextval
跳转前先比较字符,相等就继续回跳,一步到位
5第 5 页 · 前车之覆:失败从何而来

后车之鉴:构造nextval

沿 next 递推链逐层回溯,把字符相同的跳转修正为 nextval。

1
取原next
拿到失配位的 next 值,确定跳转链条起点
2
比对字符
比较失配位与 next 指向位置的字符是否相同
3
相同则改
字符相同则 nextval 继承前一位的 nextval,避免无谓比较
4
回溯递推
沿 next 链继续检查下一层,直到字符不同或为 0
5
逐位生成
从左到右扫完模式串,得到完整 nextval 数组
6第 6 页 · 后车之鉴:构造nextval

nextval的代码实现

c

在求出 next 的同一循环里多比一个字符,直接得到 nextval。

代码高亮加载中…

高亮处是 nextval 与 next 的唯一区别:求完 j 后再比一次 T[i] 与 T[j],不等照抄 next,相等则直接继承 nextval[j],把必然失败的对齐跳过。

7第 7 页 · nextval的代码实现

可视对比:next vs nextval

两套跳转表长得很像,边界一变结果就不同。逐条对照、数清比较次数,才能真正分清谁在何时占优。

next 跳转
  • 跳转值仅由模式串自身前缀推出
  • 失配后可能重复比较相同字符
  • 构造简单,坏情形多一轮无谓比较
nextval 跳转
  • 在 next 基础上追加字符相等的判断
  • 失配后直接跳过必然失败的比较
  • 构造多一步判断,坏情形省去冗余比较
当模式串字符很少重复(如随机串)时用 next 足够;当出现 aaaab 这类连续相同字符时必须用 nextval。
8第 8 页 · 可视对比:next vs nextval

自测:算一个nextval

点击作答

模式串 "aaab" 的 next 数组为 [-1,0,1,2],它的 nextval 数组是?

9第 9 页 · 自测:算一个nextval

课后思考

先合上页面自己想,再看参考答案——答案只是思考路径,不是终点。

1如果模式串里字母高度重复,比如 aaaaab,nextval 相比 next 能省掉多少次多余比较?

参考答案试着画出两者的 next/nextval 数组,逐位对比失配时的跳转目标。重复字符越多,next 盲区越大,nextval 省掉的回跳也越多。

2nextval 是不是在所有输入上都严格不慢于 next?

参考答案从跳转逻辑看:nextval 只是提前排除了注定失败的比较,跳转步数本身不变。想清楚这一点,就知道答案和边界在哪里。

3KMP 的核心思路「失配时利用已匹配的信息」,还能迁移到哪些匹配问题?

参考答案想想 Sunday、Boyer-Moore、AC 自动机等:本质都是用预处理的表记录失败后该从哪继续。找一个你能说清映射关系的场景即可。

10第 10 页 · 课后思考