KMP算法
PS:若不清楚KMP算法的运行过程,可参考 数据结构 —— KMP算法)
如果我们要对下面的主串P和模式串P进行匹配
步骤一:i=3,j=3
模式串 “abab” 对应的 next 数组为-1 0 0 1(0 0 1 2整体右移一位,初值赋为-1),当模式串 P 和主串 S 进行匹配时,发现b跟c失配,于是模式串 P 右移 j - next[ j ] = 3 - 1 =2位。
步骤二:i=3,j=1
右移2位后,b又跟c失配。事实上,因为在上一步的匹配中,已经得知 P[3] = b,与 S[3] = c失配,而右移两位之后,让P[ next[3] ] = P[1] = b 再跟 S[3] 匹配时,必然失配。问题出在哪呢?
问题在于,我们应该避免出现 p[j] = p[ next[j] ]的情况。当P[j] != s[i] 时,下次匹配必然是 P[ next [ j ] ] 跟 S[i] 匹配,如果 P[j] = P[ next[ j ] ],必然导致后一步匹配失败(因为 P[j]已经跟 S[i]失配,然后你还用跟 P[j]等同的值 P[ next [ j ] ]去跟 S[i]匹配,很显然,必然失配),所以不能允许 P[j] = P[ next [ j ]]。
由此可见,KMP 算法仍存在不足之处
- 没有利用到匹配失败时的信息(即P[j]),在使用 j=next[j] 进行回溯时进行了多余的计算
因此,我们提出了改进的KMP算法,对 next 数组进行修正,得到 nextval 数组
改进的KMP算法
步骤一:i=3,j=3
因为 next[ 3 ]=1,故P[3] = P[ next[ 3 ] ] = P[1] = b ,next[3] 需要再次递归(因为当P[3] = P[ next[ 3 ] ]时,必然失配,所以要找到 next[ next [ 3 ] ]再进行匹配;如若继续失配,则继续往前寻找,直到找到不相等的P[next…]或到达临界点 j=-1),即令 nextval[3] = next[ next [ 3 ] ] = next[1] = 0,P[ next[ 1 ] ] = P[0] = a,P[3] ≠ P[ next[ 1 ] ],故对 P[3] 和 P[ next[ 1 ] ] 进行匹配
步骤二:改进后变成了 i=3,j=0
此时,我们利用 nextval 解决了存在的问题
求 nextval 数组
上述内容介绍了改进的 KMP 算法的运行过程,那么接下来我们将介绍如何求出 KMP 算法中用到的 next 数组
(前三步具体步骤省略,可参考 数据结构 —— KMP算法)
(1)寻找最长前缀后缀
(2)得到最大长度表
(3)求出 next 数组
(4)求出 nextval 数组
将 nextval 的初值赋为 -1
当 P[ j ] = P[ next[ j ] ] 时,只需让 nextval[ j ]赋值为 nextval [ next[ j ] ]。原因有两点:
- nextval 数组 时从下标0开始逐步往后求得的,所以在求 nextval[j]时,nextval [ next[ j ] ]必定已经存在
- nextval [ next[ j ] ]包含了前面的 nextval 的比较结果,因此无须再重复比较。
参考文章