Kmp算法(源于《算法竞赛进阶指南》)
前言
Kmp算法, 又叫字符串模式匹配算法, 能在线性的时间内判断出字符串$A$是否是字符串$B$的字串, 并求出其出现的位置
Kmp算法
Kmp算法分两步
对字符串$A$进行自我匹配, 求出一个数组$next[i]$表示$A$中以$i$结尾的非前缀字串与$A$的前缀能匹配的最长长度, 即
特别地, 如果不存在这样的$j$, $next[i] = 0$
将$A$和$B$进行匹配, 求出一个数组$f[i]$表示$B$中以$i$结尾的字串能与$A$的前缀匹配的最长的长度, 即
如何快速计算$next$数组
我们递推地计算$next$数组, 假设$next[1, i - 1]$已经计算完毕, 我们现在要计算$next[i]$, 我们要求最大的$j$, 使得$j < i, A[i - j + 1, i] = A[1, j]$, 忽略最大这个条件, 我们称满足后面两个条件的$j$称为$next[i]$的候选项
引理
若$j_0$是$next[i]$的一个候选项, 那么小于$j_0$的最大的$next[i]$的候选项是$next[j_0]$
证明
如图所示, 由$next$数组的定义可知红色的区间一定是相同的, 我们现在要在以$i$结尾的红色区间上找一段最长的后缀区间(绿色)与$A$的前缀相同, 由于两个红色区间完全相同, 这个操作可以转移到以$j0$结尾的红色区间上进行, 那我们就是要找一个最大的$j_1$, 满足$j_1 < j_0, A[j_0 - j_1 + 1, j_0] = A[1, j_1]$, 这恰好就是$next[j_0]$的定义!!!
利用引理计算$next$数组
根据引理, 我们计算完$next[i - 1]$时, $next[i - 1]$的所有候选项从大到小依次是$next[i - 1], next[next[i - 1]], …$, 如果一个整数$j$是$next[i]$的候选项, 那么$j - 1$必然是$next[i - 1]$的候选项, 所以在计算$next[i]$时, 我们只需要将$next[i - 1] + 1, next[next[i - 1]] + 1, …$当作$next[i]$的候选项就可以了, 我们依次从大到小枚举$next[i - 1]$的候选项, 直到遇到一个候选项$j, A[j + 1] = A[i]$, 那么有$next[i] = j + 1$, 代码中由于$j$对于$i$是全局变量, 所以这个时候应该$j ++$
$f$数组
与$next$数组类似
代码
1 |
|
时间复杂度
在计算$next$数组时, $j$最多加了$n$次
whille
时, $j$可能递减多次, 但是由于$j$一直非负, 所以$j$减小的幅度不会超过$j$增加的幅度
计算$f$时同理
时间复杂度$O(n + m)$