KMP 通俗讲解
本文原计划于
最近有点颓,写不动题目了,来水一篇博客。
KMP 是 Knuth–Morris–Pratt 算法的简称,由 Knuth、Pratt 和 Morris 在 1977 年共同发布。该算法用来处理字符串匹配的问题(就是给定一个字符串,求它的所有字串中另一个字符串相等的字符串)。
网上有很多讲解 kmp 的文章了,但其实 kmp 有着(自认为)更简单的理解方法。本文记文本串为
首先考虑暴力匹配的过程。
如图,
考虑下一个可以和
-
$$\begin{align} S=&{\color{red}{\texttt{ab}}} {\color{yellow}\texttt{a}}{\color{red}{\texttt{aba}}}\texttt{abacbab}\\ T=&{\color{yellow}\texttt{a}}\texttt{baabacbab} \end{align}$$ 显然这种情况没什么用。因为这个匹配上的字串的长度甚至连原来匹配上的长度都比不上,最终自然不能匹配上整个 $T$。 -
$$\begin{align} S=&{\color{red}{\texttt{aba}}}{\color{yellow}\texttt{aba}}\texttt{abacbab}\\ T=&{\color{yellow}\texttt{aba}}\texttt{abacbab} \end{align}$$ 这种情况是**有可能**匹配上整个串的,因为上一次匹配时就匹配到了第六个字符,因此程序处理到这里的时候也不知道后面能不能匹配上。 那么,这种字串有什么特点吗? 注意到,由于这两个字串都匹配上了 $T$ 的某个前缀,因此这两个字串的前 $i(1\lt i \lt \min(|s1|,|s2|))$ 个字符是相等的。又因为 $s_2$ 同时也是 $s_1$ 的后缀,可以得到 $s_2$ 是 $s_1$ 的一段相等的前后缀(“Border“)。 -
$$\begin{align} S=&{\color{red}{\texttt{abaaba}}}{\color{yellow}\texttt{aba}}\texttt{cbab}\\ T=&{\color{yellow}\texttt{aba}}\texttt{abacbab} \end{align}$$ 这种情况显然通过任何已知信息都无法推出了。
由上述分类可以发现,下一个可能匹配上的位置只和已经匹配上的字符串有关。由于这个字符串必然是模式串的某个前缀,我们可以预处理模式串的所有前缀的 Border。但是那么多 Border,选哪一个呢?容易发现,选择最长的 Border 可以保证不漏。因为加入选择较短的 Border,如果较长的 Border 合法,则不能再次回到较长的 Border,因为每次指针都会向后面移动。而如果较短的 Border 合法,必然可以通过上述方法得到。因此我们只需要预处理出模板串的最长 Border 即可。
先假设求出了最长的 Border,我们又该怎么做呢?
首先,令一个指针
- 初始化:
i\gets 0,k\gets0 。 - 先暴力匹配。如果
S_{i+1}=T_{k+1} ,则k\gets k+1,i\gets i+1 。 - 如果失配了,即
S_{i+k+1}\not=T_{k+1} ,将k\gets B_k ,然后继续匹配。 - 如果
k=-1 ,直接令i\gets i+1,k\gets0 ,相当于暴力匹配时失配的处理方式。 - 如果
k=|T| ,说明找到了一个字串。如果还要寻找更多的字串,可以k\gets B_k ,然后继续匹配。
这个过程的复杂度是O(|S|) ,证明可见 OI-Wiki。
我们在暴力匹配是,将
考虑这个东西的实际意义,应该是代表
我们将初始化改成
因此 KMP 的总复杂度就是
代码:网上太多了,因此就不给了。
全文完。
讲个笑话,我自己至今还没写过 KMP 的模板。
顺便,看看我的博客。