Knuth-Morris-Pratt KMP Algorithm
基本思想
- 将pattern和text从左到右进行比较,但与brute-force算法相比,KMP能更智能地转换pattern。
- 当不匹配发生时,为了避免冗余的比较,我们能滑动pattern的位置最多是多少?
- 假设问题是在一段Text(T)中找到Pattern(P)为abaaba的字符串,i 和 j 为 T 和 P 的 index,则如上答案,滑动pattern 最大的距离为 P[0…j-1] 的最大前缀同时也是 P[1…j-1] 的后缀。
这里有一些理解上的难度,回过头来看在Text中找到Pattern为abaaba的情况。一个基本事实是当 T 中的 x
与 P 中的 a
不匹配时,已经知道前面五个字符 abaab 是匹配的。而KMP算法则利用已经比较过的字符串,把 pattern 往后继续滑,跳过已经比较过的位置。
KMP Failure Function
原理
- KMP算法预处理pattern。
- KMP算法的预处理部分,Failure function F(j) 被定义为 P[0…j] 的最大子前缀,同时也是
P[1…j] 的后缀的 size。 - KMP算法将brute-force改变为,如果T和P在 P[j] != T[i] 处不匹配,则设 j ⇐ F(j-1),然后滑动。
- 这咋整呢?借助部分匹配表(Partial Match Table)!
通俗来讲,即,移动位数 = 已匹配的字符数 - 对应的部分匹配值
。这里 j 的最后一位对应的a
不行,所以找到 j=5 时 F(j)=3,滑3位。
部分匹配表(Partial Match Table)
这算是KMP算法中最核心的部分之一了。个人认为任何不涉及PMT的KMP算法介绍都是耍流氓。。。
首先,要了解两个概念:“前缀"和"后缀”。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。(摘自阮一峰大佬)
以刚才的abaaba为例子,看一下前缀(prefix),后缀(suffix),最长子串(max-substring),最长子串长度(len-max-substring)
prefix | suffix | max-sub | len-maxsub | |
---|---|---|---|---|
a | null | null | null | 0 |
ab | a | b | null | 0 |
aba | a,ab | ba,b | a | 1 |
abaa | a,ab,aba | baa,aa,a | a | 1 |
abaab | a,ab,aba,abba | baab,aab,ab,b | ab | 2 |
abaaba | a,ab,aba,abaa,abaab | baaba,aaba,aba,ba,a | aba | 3 |
岂不美哉!
有了PMT表,可以往后延伸到所谓的Next表。
next表
Next 表是由 PMT 表得到的,做到就是將 PMT 表中的每一個值向后移动 1 位,第一位賦值為 -1。(摘自知乎–小撸伴读)
pattern | a | b | a | a | b | a |
---|---|---|---|---|---|---|
PMT | 0 | 0 | 1 | 1 | 2 | 3 |
Next | -1 | 0 | 0 | 1 | 1 | 2 |
而这Next表的作用就是刚才讲的:KMP算法将brute-force改变为,如果T和P在 P[j] != T[i] 处不匹配,则设 j ⇐ F(j-1),然后滑动。
伪代码
Algorithm KMPMatch(T, P)
F ⇐ failureFunction(P)
i ⇐ 0
j ⇐ 0
while i < length(T)
if T[i] = P[j] then
if j = length(P) - 1 then
return i - j { match }
else
i ⇐ i + 1
j ⇐ j + 1
else
if j > 0 then
j ⇐ F[j - 1]
else
i ⇐ i + 1
return -1 { no match }
在上述pseudo code中,有:
- failureFunction 可以用数组表示,在时间复杂度O(m)内计算;
- 每次while-loop的迭代中,
- i increases by 1
- 或者在 i-j 范围内滑动,这里 i-j increases by at least one,(observe that F(j - 1) < j)
- 因此,在while-loop中,迭代的次数不会超过2n;
- 综上,KMP算法理想状态下,或者说大部分情况下的时间复杂度是O(m+n)。
示例
图解
Java代码实现
用BoyerMoore算法的话,
KMP算法的实现
然后