(c3)KMP算法:理解next[]表
KMP算法:理解next[]表
看完你能亲手推导任意模式串的next[]表,并讲清匹配为何不回退
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
KMP算法:理解next[]表
看完你能亲手推导任意模式串的next[]表,并讲清匹配为何不回退
暴力匹配怎么走
KMP之前先看暴力匹配:逐位比较,失配就右移一格重来。
暴力匹配的两大浪费
上一页我们看到暴力匹配能跑通,但主串指针 i 匹配失败后要退回去重新数。就像读一本书读到一半忘了页码,只能翻回第一页重读——问题到底出在哪两处?
明明记得读到哪句话,却翻回页首重新逐字找
失配后必须从头比吗
模式串在位置 j 失配时,已匹配的前缀中藏着哪条可直接利用的信息?
已匹配部分的最长公共前后缀
上页我们说了失配后不必从头比。那到底能跳多远?答案藏在失配点之前的那段已匹配文本里——看它的开头和结尾有多少是「重复」的。
复盘时棋局后段和开局重复的部分,正是可以保留不再推演的
next[]表:跳跃的路标
沿箭头走一遍失配流程:j 处失配后,查表、跳跃、原地继续比,主串指针永不回头。
按next[]表匹配主串
用 next[] 表重跑同一组匹配:主串指针 i 永不回退,失配时只有模式串指针 j 跳到 next[j]。
失配时 j=next[j-1],利用已匹配前缀的公共前后缀直接对齐,主串指针 i 从不回头——这正是相对暴力匹配省时的全部来源。
暴力 vs KMP的指针行为
两者失配时都动模式串指针,但主串指针是否回退,是初学最容易混淆的关键差异。
- 失配后主串指针 i 回退到本轮起点的下一位置
- 模式串指针 j 直接归零,从头再比
- 已匹配的比对结果全部丢弃,重复劳动
- 失配后主串指针 i 永不回退,只增不减
- 模式串指针 j 跳到 next[j],利用历史信息
- 已匹配部分的最长公共前后缀被保留复用
不回溯的直觉
上一页我们看到:KMP 的主串指针从不后退。凭什么敢不回头?因为失配那一刻,已匹配的前缀本身携带了结构信息——next[] 表已经把这段信息「吸收」干净,主串不需要再被看第二遍。
读到第几页记住了页码,续读直接翻回,无需重翻全书找位置
走一个完整例子
用主串 ABABCABABD 匹配模式 ABABD,逐步验证主串指针从不回头。
为什么恰好是线性复杂度
走完整个例子后你会发现:KMP 从没让主串指针回头。线性复杂度不是算出来的巧合,而是「主串指针只前进」这一条纪律的直接推论。
上楼消耗的每级台阶,都在预付下楼要走的路
通配哨兵:next[0]=-1
按 next[] 跳跃时有个尴尬时刻:模式串开头就失配,j 已经是 0,没有更靠前的位置可跳了。难道要写一段特殊代码处理这种情况?更聪明的做法是——放一个万能哨兵统一收编。
-1 是跑道外的牵引区,车滑到头就从这里重新起飞,不算异常
递推构造next[]表
利用「模式串自己匹配自己」递推算出next[],每一步只依赖前面的结果。
手推一张完整的next[]表
用 Python 逐位计算模式串 "ABABC" 的 next[],每步打印依据,验证递推构造。
高亮行就是递推构造:k 沿已算好的 next 链回退找可延长的公共前后缀,nxt[i]=k+1 写入答案,输出与手推结果一致。
自测:算出next值
采用 next[0]=-1 的约定,模式串 "ababaa" 中位置 5(字符 'a')的 next 值是多少?即前缀 "ababa" 的答案。
要点收束
- ✓主串指针只进不退,回溯全交给模式串
- ✓next[j]=已匹配段最长公共前后缀长度
- ✓哨兵-1把边界情况并入同一套移动逻辑
- ✓构造next本身用递推,均摊O(1)一次比较
- ✓失配跳跃总代价均摊后为O(n+m)
课后思考
先自己想,别急着翻答案——想不动了再看参考思路。
参考答案可优化为nextval:若跳到的新字符与失配字符相同,跳过去必然再失配,可继续沿用其next值。想想递推时如何顺便比较这两处字符。
参考答案主串走完时j值就是最长后缀匹配长度,可用于求周期、循环子串等。多模式串则需AC自动机——把next思想推广到Trie上的失配指针。
参考答案j=-1时主串i与模式串j同时右移(i++、j=0),等价于从主串下一字符重新开始——这就是-1而非0的原因。试着用"模式串比主串长"等极端输入推一遍。