来源
在暴力匹配子串时,若不匹配,则跳回子串开头匹配主串的下一位。
会发现其实主串的部分位置已经遍历过了,这样重复的遍历导致了暴力算法并不优秀。
我们可以让子串跳过一部分主串,以达到更高的效率。
实现
我们定义一个数组 $nx$ ,表示如果子串在第 $i$ 位失配,应该把子串向前移到 $nx_i$ 位继续匹配。
设子串为 $b$,$nx_i$ 的值为 $j$,满足 $b_0$$b_j$ 与 $b_{i-j+1}$$b_i$ 相等,这样就可以减少重复匹配。
于是我们遍历主串,同时匹配子串,如果失配就跳,成功匹配就匹配子串下一位。
如果子串匹配结束,那么说明找到了一个子串,记录开头位置并继续匹配。
1 | inline void kmp() |
如何求得 $nx$ 呢?
我们可以将子串与自己匹配,在过程中也利用了已经求好的 $nx$ 值。
显然 $nx_0=0$。
1 | inline void init() |
可以看出和匹配几乎是一模一样的。
应用
求给定子串出现位置
求最长重复子串
即 $nx$ 的最大值
求主串最多由多少个相同子串不重叠连接组成(复读)
子串长度 $k=len-nx_{len-1}$。
若 $k|len$ 则子串个数 $n=len/k$,否则 $n=1$。