好吧,快 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:失配时的状态

图 1:已匹配 \(n\) 位后,第 \(n+1\) 位失配

然后就是步骤了。

  1. 那么是不是你 \(S_{n+1}\) 和 \(T_{y+1}\) 相同,那么是不是匹配成功了,能继续匹配。因为你能匹配到这里说明之前的都已经完成匹配,都是匹配上的。

  2. 如果不相同怎么办?border 的妙用来了。你直接把 \(S_{m+1}\) 和 \(T_{y+1}\) 匹配就可以了,如果相同就继续匹配,如果不相同就重新跳到 \(S[1\cdots m]\) 的最长 border。

  3. 如果 \(n\) 退到最前面仍失配,则 \(y\) 后移一位。

图 2:三种情况

图 2:每次比较后的三种走向


证明

点击展开证明

因为你的 border 是最长相等的前后缀。所以在 \(S\) 中 \([1,m]\) 和 \([n-m+1,n]\) 这两段是相等的。(因为 \(m\) 既是表示 border 长度,也表示这个串的结尾下标是 \(m\) 位置)

所以 border 的跳跃你可以理解为将 \([1,m]\) 向右平移了,直至与 \([n-m+1,n]\) 重合(两段相等,必定重合,\(m\) 与 \(n\) 对齐)。

图 3:右移到 border

图 3:右移 \(n-m\) 位后,三段紫色部分对齐且相等

还有就是为什么从 border 的 \(m\) 开始配,而不从其他地方开始配呢?

用反证法。假设把 \(S\) 右移 \(k\) 位(\(0<k<n-m\))后,仍能与 \(T\) 匹配,那么

\[S[1\cdots n-k]=T[y-n+k+1\cdots y]=S[k+1\cdots n] \]

即 \(S[1\cdots n-k]\) 既是 \(S[1\cdots n]\) 的真前缀又是真后缀,是一个 border,且长度 \(n-k>m\)。这与"\(m\) 是最长 border"矛盾。

图 4:反证

图 4:右移 \(k\) 位(\(k<n-m\))会得到更长的 border

所以从 border 开始跳时最好的。