
与每趟匹配失败都从头开始重新比较的暴力匹配算法不同,KMP 算法会按照已记录的数组移动到指定位置开始比较,能大幅提高效率。而该数组记录的内容仅与模式串本身结构相关,与主串无关。
理论知识
设:
ababcabcacbab
为主串,i
为主串的遍历指针abcac
为子串,j
为子串(也称模式串)的遍历指针pm 数组
为对应字符串的部分匹配值next 数组
由pm 数组
右移一位得到,其中规定next[0] = -1
index | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
char | a | b | c | a | c |
pm | 0 | 0 | 0 | 1 | 0 |
next | -1 | 0 | 0 | 0 | 1 |
在 KMP 算法匹配过程中,假设第 j 位失配(从 0 编号)
已知:移动位数 = 已经匹配的字符数(0 ~ j-1 共 j 个) - 最后一个匹配字符的部分匹配值(pm[j-1])
记为:move = j - pm[j-1]
替换:move = j - next[j]
则 j
需要回退到 j - move
位置,即 j = j - move = j - (j - next[j]) = next[j]
,即 j
回退到 next[j]
位置
具体匹配过程:
第一次匹配(失配时
i = 2, j = 2
)i: 0 1 2 主串: a b a
b c a b c a c b a b 子串: a b c
j: 0 1 2 子串应向后移动 2 位,
j = 2
—->j = 0
(next[2] == 0
),i = 2
—->i = 2
( i 不变)第二次匹配(失配时
i = 6, j = 4
)i: 0 1 2
3 4 5 6 主串: a b a b c a b
c a c b a b 子串: # # a b c a c
j: 0
1 2 3 4 子串应继续向后移动 3 位,
j = 4
—->j = 1
(next[4] == 1
),i = 6
—->i = 6
( i 不变)第三次匹配(匹配成功
i = 10, j = 5
)i: 0 1 2 3 4 5 6
7 8 9 10 主串: a b a b c a b c a c b a b 子串: # # * * * a b c a c j: 0 1
2 3 4 5 匹配成功,返回子串在主串中的首位置:
i - j = 5
所以 KMP 算法如何实现的问题就转化为如何构建 next 数组的问题。
手动如何求 next 数组呢?
- 法一:先求部分匹配值,再右移得到 next 数组
- 法二:由 [0…j-1]个字符(已经匹配成功的字符)组成的串的最长相等前后缀长度就是
next[j]
注:不同书中定义的 next 数组含义不同,有的是没有右移之前的,有的是右移一位的(如本文),有的是右移一位后数值再加 1 的,要具体情况具体分析。另外,本文中数组从 0 开始存字符,有的书可能从 1 开始,对应的代码会有些许差别。
代码实现
第一步:求 next 数组
1 | void getNextTable(string pattern,int nextTable[]){ |
第二步:根据 next 数组编写 KMP 算法
1 | int KMP(string pattern, string str,int nextTable[]){ |
第三步:测试
1 |
|
算法改良
假设 j == 3
时失配,而 next[3] == 2, next[2] == 1, next[1] == 0, next[0] == -1
(由计算 next 数组的规则可知,此时模式串中第 0、1、2 个字符必相同),则 j
需要依次回退到 2
、1
、0
,我们可以改善 next 数组,让 j
直接回退到 0
位置,减少无效判断次数。
用 nextval数组
表示改良后的 next数组
,过程如下:
- 先算出 next 数组(
next[0] = -1
) - 如果第 j 个字符和 j 的 next 所指向的字符相同,则它们的 nextval 值相同;否则让 j 的 nextval 值与 next 值相等。
代码如下:
1 | void getNextvalTable(string pattern,int nextvalTable[]){ |
KMP 算法只要将其调用的函数 getNextTable
改为 getNextvalTable
,其余保持不变。
手动计算部分匹配值
部分匹配值
为字符串的前缀和后缀的最长相等前后缀长度,以 ababa
为例:
a
的前缀和后缀都为空集,最长相等前后缀长度为 0ab
的前缀为 { a },后缀为 { b } ,交集为空集,最长相等前后缀长度为 0aba
的前缀为 { a, ab },后缀为 { a, ba },交集为 { a },最长相等前后缀长度为 1abab
的前缀为 { a, ab, aba },后缀为 { b, ab, bab },交集为 { ab },最长相等前后缀长度为 2ababa
的前缀为 { a, ab, aba, abab },后缀为 { a, ba, aba, baba },交集为 { a, aba },最长相等前后缀长度为 3
所以字符串ababa
的部分匹配值为00123
参考
《王道数据结构考研复习指导》