好吧,快 S 组复赛了,来复习一下 KMP 的知识点,既是写给我的,也是写给所有人的。零基础也能看哦/bx。
KMP 原理
首先 KMP 最基本的用法是解决单模式串匹配。
什么是单模式串匹配
比如你有两个子串 \(S,T\)。
查找一个子串在另一个子串的出现次数,也可以他们最长的相同的子串有多长,最基本的就这些。
想要学会 KMP 的话,最先了解的一定就是 border。
大家肯定都听过这个吧想必(没听过也没关系,跟着我的思路来)。
border 是什么
border 的解释:border 要求既是真前缀又是真后缀,并不要求最长,一个串可以有多个 border,KMP 但是用的是最长的那个。
ps: 后文的 border 均指最长 border。
什么是真前后缀
就是不包括自己这个字符串本身的前缀或者后缀。
就相当于你的这一个字符串要既是这个匹配串的前缀,又得是后缀,又不能是本身,又长度最大。看起来相当的绕。
我们可以注意拆解。
为什么要 border
暴力肯定人人都会吧。每次遇到不匹配的情况都从头开始匹配,有点浪费。
比如说给你一个字符串 \(S\),以及默认你已经求出了它的已匹配部分 \(S[1\cdots n]\) 的最长 border \(B\),如图。
- 已匹配了 \(S\) 的前 \(n\) 位。
- \(B\) 的长度为 \(m\)。
- 你现在的待匹配字符串是 \(T\),匹配到的位置是 \(y\)。
- \(S[1\cdots n]=T[y-n+1\cdots y]\)。

图 1:已匹配 \(n\) 位后,第 \(n+1\) 位失配
然后就是步骤了。
-
那么是不是你 \(S_{n+1}\) 和 \(T_{y+1}\) 相同,那么是不是匹配成功了,能继续匹配。因为你能匹配到这里说明之前的都已经完成匹配,都是匹配上的。
-
如果不相同怎么办?border 的妙用来了。你直接把 \(S_{m+1}\) 和 \(T_{y+1}\) 匹配就可以了,如果相同就继续匹配,如果不相同就重新跳到 \(S[1\cdots m]\) 的最长 border。
-
如果 \(n\) 退到最前面仍失配,则 \(y\) 后移一位。

图 2:每次比较后的三种走向
证明
点击展开证明
因为你的 border 是最长相等的前后缀。所以在 \(S\) 中 \([1,m]\) 和 \([n-m+1,n]\) 这两段是相等的。(因为 \(m\) 既是表示 border 长度,也表示这个串的结尾下标是 \(m\) 位置)
所以 border 的跳跃你可以理解为将 \([1,m]\) 向右平移了,直至与 \([n-m+1,n]\) 重合(两段相等,必定重合,\(m\) 与 \(n\) 对齐)。

图 3:右移 \(n-m\) 位后,三段紫色部分对齐且相等
还有就是为什么从 border 的 \(m\) 开始配,而不从其他地方开始配呢?
用反证法。假设把 \(S\) 右移 \(k\) 位(\(0<k<n-m\))后,仍能与 \(T\) 匹配,那么
即 \(S[1\cdots n-k]\) 既是 \(S[1\cdots n]\) 的真前缀又是真后缀,是一个 border,且长度 \(n-k>m\)。这与"\(m\) 是最长 border"矛盾。

图 4:右移 \(k\) 位(\(k<n-m\))会得到更长的 border
所以从 border 开始跳时最好的。