介绍
KMP算法是一种高效的字符串模式匹配算法,也就是俗称字符串查找算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时提出,KMP这个名字取自这三位的名字首字母。
内容
一、字符串模式匹配
有两个字符串T和P,要在串T中,查找是否有与串P相等的子串,则称串T为目标,称P为模式,并称这个查找运算为字符串模式匹配。
二、朴素的模式匹配:B-F算法
这儿先对字符串的字符位置进行编号,字符串的第1个字符对应的字符位置编号为0,第2个字符对应的字符位置编号为1,如此递增,字符串第 n n n个字符对应的字符位置编号为 n − 1 n - 1 n−1。长度为 l l l的字符串的字符位置编号为从0到 l − 1 l - 1 l−1的整数。
这个算法的思想是先将模式串P与目标串T的第0个字符位置对齐,然后将从目标串第0个字符位置开始的与模式串P同长度的的串,与模式串P逐个字符比较,看是否相等。如果相等,则找到了匹配的子串,算法终止;如果不相等,则将模式串P与目标串T的第1个字符位置对齐,将从目标串第1个字符位置开始的与模式串P同长度的的串,与模式串P逐个字符比较,再看是否相等。不停的将目标串T中与模式串P对齐的字符位置后移一个位置,得到与模式串P同长度的子串,然后与模式串P比较,直到找到匹配的子串,如果全部比较完后,都没有找到,则说明目标串T中没有与模式串P匹配的子串。
这个算法由Brute和Force提出,所以称为B-F算法。
如果目标串T的长度为 l T l_T lT,而模式串P的长度为 l P l_P lP,且 l T > l P l_T > l_P lT>lP,这个算法在最坏时要进行 l T − l P + 1 l_T - l_P + 1 lT−lP+1轮子串对比,然后每一轮子串对比最坏时要进行 l P l_P lP次单个字符比较,理论上的最坏比较次数为 l P ∗ ( l T − l P + 1 ) l_P * (l_T - l_P + 1) lP∗(lT−lP+1),这个算法的时间复杂度就是 O ( l T ∗ l P ) O(l_T * l_P) O(lT∗lP)。
三、改进的模式匹配:KMP算法
1. 考察B-F算法
B-F算法在缺点在于每一轮只将对齐位置在目标串T中后移一个位置,而且每轮都要子串比较,这样导致了低效。KMP算法在考察了模式串P的特征后,在B-F算法上进行了改进,每次可以将对齐位置后移若干个位置,另外,通过去掉在上一次子串对比中已经比较过的对比,还能减少比较。最后效果,相当于对目标串只做了一次目标串长度的比较。
设目标串T从头到尾的组成为 t 0 t 1 t 2 . . . t l T − 1 t_0t_1t_2...t_{l_T - 1} t0t1t2...tlT−1,设模式串P从头到尾的组成为 p 0 p 1 p 2 . . . p l P − 1 p_0p_1p_2...p_{l_P - 1} p0p1p2...plP−1。
现在考察B-F算法,设上一轮子串比较在目标串T中的对齐位置为 s s s,然后在上一轮子串比较中,比较到模式串P的第 j j j个字符位置时不等,如下所示:
T | t 0 t_0 t0 | t 1 t_1 t1 | … | t s − 1 t_{s - 1} ts−1 | t s t_s ts | t s + 1 t_{s + 1} ts+1 | … | t s + j − 1 t_{s + j - 1} ts+j−1 | t s + j t_{s + j} ts+j | … | t s + l P − 1 t_{s + l_P - 1} ts+lP−1 | … | t l T − 1 t_{l_T - 1} tlT−1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
= | = | = | = | ≠ \not = = | |||||||||
P | p 0 p_0 p0 | p 1 p_1 p1 | … | p j − 1 p_{j - 1} pj−1 | p j p_j pj | … | p l P − 1 p_{l_P - 1} plP−1 |
这时,应有
t
s
t
s
+
1
.
.
.
t
s
+
j
−
1
=
p
0
p
1
.
.
.
p
j
−
1
(
1
)
t_st_{s + 1}...t_{s + j - 1} = p_0p_1...p_{j - 1} \space \space \color{red}{(1)}
tsts+1...ts+j−1=p0p1...pj−1 (1)
按B-F算法,下一轮子串比较应将模式串P与目标串T中的
s
+
1
s + 1
s+1字符位置对齐比较,这轮若要满足匹配,则必须满足
p
0
p
1
.
.
.
p
j
−
2
p
j
−
1
.
.
.
p
l
P
−
1
=
t
s
+
1
t
s
+
2
.
.
.
t
s
+
j
−
1
t
s
+
j
.
.
.
t
s
+
l
P
(
2
)
p_0p_1...p_{j - 2}p_{j - 1}...p_{l_P - 1} = t_{s + 1}t_{s + 2}...t_{s + j - 1}t_{s + j}...t_{s + l_P} \space \space \color{red}{(2)}
p0p1...pj−2pj−1...plP−1=ts+1ts+2...ts+j−1ts+j...ts+lP (2)
而根据上一轮子串比较,我们已经知道
(
1
)
(1)
(1)式成立,由
(
1
)
(1)
(1)式可得
t
s
+
1
t
s
+
2
.
.
.
t
s
+
j
−
1
=
p
1
p
2
.
.
.
p
j
−
1
(
3
)
t_{s + 1}t_{s + 2}...t_{s + j - 1} = p_1p_2...p_{j - 1} \space \space \color{red}{(3)}
ts+1ts+2...ts+j−1=p1p2...pj−1 (3)
将这个
(
3
)
(3)
(3)式代入上面必须满足的
(
2
)
(2)
(2)式,则必须满足的等式变为
p
0
p
1
.
.
.
p
j
−
2
p
j
−
1
.
.
.
p
l
P
−
1
=
p
1
p
2
.
.
.
p
j
−
1
t
s
+
j
.
.
.
t
s
+
l
P
(
4
)
p_0p_1...p_{j - 2}p_{j - 1}...p_{l_P - 1} = p_1p_2...p_{j - 1}t_{s + j}...t_{s + l_P} \space \space \color{red}{(4)}
p0p1...pj−2pj−1...plP−1=p1p2...pj−1ts+j...ts+lP (4)
在上式中,只看模式串P,则模式串P需要满足关于自身的特征等式
p
0
p
1
.
.
.
p
j
−
2
=
p
1
p
2
.
.
.
p
j
−
1
(
5
)
p_0p_1...p_{j - 2} = p_1p_2...p_{j - 1} \space \space \color{red}{(5)}
p0p1...pj−2=p1p2...pj−1 (5)
若模式串P不满足上面的特征等式,则与目标串T的 s + 1 s + 1 s+1字符位置对齐的比较不需要进行,可以直接忽略这一轮,因为肯定不匹配。
同理类推,对于与目标串T的
s
+
2
s + 2
s+2字符位置对齐的比较需不需要进行,只需考察关于模式串P是否满足如下特征等式
p
0
p
1
.
.
.
p
j
−
3
=
p
2
p
3
.
.
.
p
j
−
1
(
6
)
p_0p_1...p_{j - 3} = p_2p_3...p_{j - 1} \space \space \color{red}{(6)}
p0p1...pj−3=p2p3...pj−1 (6)
也就是直到,对于模式串P,从大到小找到第1个满足关于自身的相应特征等式的整数
k
k
k为止。也就是找到一个整数
k
k
k,使得同时满足下面的要求
0
≤
k
<
j
−
1
(
7
)
0 \leq k < j - 1 \space \space \color{red}{(7)}
0≤k<j−1 (7)
p
0
p
1
.
.
.
p
k
=
p
j
−
k
−
1
p
j
−
k
.
.
.
p
j
−
1
(
8
)
p_0p_1...p_k = p_{j - k - 1}p_{j - k}...p_{j - 1} \space \space \color{red}{(8)}
p0p1...pk=pj−k−1pj−k...pj−1 (8)
p
0
p
1
.
.
.
p
k
p
k
+
1
≠
p
j
−
k
−
2
p
j
−
k
−
1
.
.
.
p
j
−
2
p
j
−
1
(
9
)
p_0p_1...p_kp_{k + 1} \not = p_{j - k - 2}p_{j - k - 1}...p_{j - 2}p_{j - 1} \space \space \color{red}{(9)}
p0p1...pkpk+1=pj−k−2pj−k−1...pj−2pj−1 (9)
才有
t
s
+
j
−
k
−
1
t
s
+
j
−
k
.
.
.
t
s
+
j
−
1
=
p
j
−
k
−
1
p
j
−
k
.
.
.
p
j
−
1
=
p
0
p
1
.
.
.
p
k
(
10
)
t_{s + j - k - 1}t_{s + j - k}...t_{s + j - 1} = p_{j - k - 1}p_{j - k}...p_{j - 1} = p_0p_1...p_k \space \space \color{red}{(10)}
ts+j−k−1ts+j−k...ts+j−1=pj−k−1pj−k...pj−1=p0p1...pk (10)
才可能有
t
s
+
j
−
k
−
1
t
s
+
j
−
k
.
.
.
t
s
+
j
−
1
t
s
+
j
.
.
.
t
s
+
l
P
+
j
−
k
−
2
=
p
j
−
k
−
1
p
j
−
k
.
.
.
p
j
−
1
t
s
+
j
.
.
.
t
s
+
l
P
+
j
−
k
−
2
=
p
0
p
1
.
.
.
p
k
p
k
+
1
.
.
.
p
l
P
−
1
(
11
)
t_{s + j - k - 1}t_{s + j - k}...t_{s + j - 1}t_{s + j}...t_{s + l_P + j - k - 2} = p_{j - k - 1}p_{j - k}...p_{j - 1}t_{s + j}...t_{s + l_P + j - k - 2} = p_0p_1...p_kp_{k + 1}...p_{l_P - 1} \space \space \color{red}{(11)}
ts+j−k−1ts+j−k...ts+j−1ts+j...ts+lP+j−k−2=pj−k−1pj−k...pj−1ts+j...ts+lP+j−k−2=p0p1...pkpk+1...plP−1 (11)
这一轮才不能确定不匹配,才需要比较。
这样,相当于在目标串T的 s s s字符位置这轮对齐子串比较中,比较到模式串P的第 j j j个字符位置不等后,根据模式串P的特征,我们在后一轮直接跳到了目标串T的 s + j − k − 1 s + j - k - 1 s+j−k−1字符位置对齐进行子串比较,也就是在目标串中跳了 j − k − 1 j - k - 1 j−k−1个位置。另外,由于根据上一轮的比较与模式串P特征,已经知道 ( 10 ) (10) (10)式成立,故 ( 10 ) (10) (10)式中的部分不需要再次比较。所以,这一轮我们只需要从目标串T的 s + j s + j s+j字符位置开始与模式串P的 k + 1 k + 1 k+1字符位置开始进行子串比较,从而这一轮减少了 k k k次比较。并且对于目标串T而言,这一轮开始比较字符位置就是上一轮比较到的不等字符位置,所以整个算法大致相当于只需要进行目标串长度为次数的单个字符比较,其时间复杂度降为 O ( l T ) O(l_T) O(lT)。
2. 模式串P的next函数
2.1 next函数的定义
于是,根据上面的分析,对于每一轮子串比较,只需要根据上一轮比较的情况,与模式串P自身的特征便可以优化当前这一轮的比较,从模式串P中选取位置与目标串T中上一次不等的位置开始对齐比较。可以根据上一轮比较中模式串P中的不等位置,得出这一轮中,模式串P应该选取来开始比较的位置,这个对应关系就称为模式串P的 n e x t next next函数。
设模式串P从头到尾的组成为
p
0
p
1
p
2
.
.
.
p
l
P
−
1
p_0p_1p_2...p_{l_P - 1}
p0p1p2...plP−1,则它的
n
e
x
t
next
next函数定义为:
n
e
x
t
(
j
)
=
{
−
1
,
当
j
=
0
时
k
+
1
,
当
同
时
满
足
0
≤
k
<
j
−
1
和
p
0
p
1
.
.
.
p
k
=
p
j
−
k
−
1
p
j
−
k
.
.
.
p
j
−
1
的
最
大
整
数
k
存
在
时
0
,
其
它
情
况
next(j) = \left\{ \begin{array}{rcl} -1, && 当j = 0时\\ k + 1, && 当同时满足0 \leq k < j - 1和p_0p_1...p_k = p_{j - k - 1}p_{j - k}...p_{j - 1}的最大整数k存在时\\ 0, && 其它情况 \end{array} \right.
next(j)=⎩⎨⎧−1,k+1,0,当j=0时当同时满足0≤k<j−1和p0p1...pk=pj−k−1pj−k...pj−1的最大整数k存在时其它情况
对于上面 n e x t next next函数定义,说明如下:
-
j j j是从0到 l P − 1 l_P - 1 lP−1的整数, n e x t ( j ) next(j) next(j)是从-1到 l P − 2 l_P - 2 lP−2的整数。
-
当 j = 0 j = 0 j=0时,也就是上一轮子串比较中,模式串中0号位置字符就与目标串T中对应的位置字符不等,所以这一轮比较中,应该从目标串T的上一轮比较中的不等位置的后一个位置开始与模式串P中的0号位置对齐比较。由于由next函数选出的模式串P中的位置是用来与目标串T中上一轮比较中不等位置对齐的,所以应该是模式串P中的0号位置前一个位置,这个位置其实不存在,这儿约定使用逻辑上0的前一个位置-1。这个可以理解为,模式串P中-1号位置与目标串T中上一轮比较的不等位置对齐,然后模式串P中的0号位置便是与目标串T中上一轮比较的不等位置的下一个位置对齐。
-
当 j = 1 j = 1 j=1时,不等式 0 ≤ k < 0 0 \leq k < 0 0≤k<0是矛盾的,所以这时取值是0。这种情况,在实际上,是上一轮比较中模式串P的1号位置与目标串T中对应位置不等,也就是模式串中第2个字符不等,这种情况这轮应该将模式串P与T中的不等位置对齐,也就是用模式串中的0号位置来对齐。这种情况取0与实际情况是吻合的。
-
当 j > 1 j > 1 j>1时,不等式 0 ≤ k < j − 1 0 \leq k < j - 1 0≤k<j−1不是矛盾的,这时便是按之前考察的情况,找出满足 p 0 p 1 . . . p k = p j − k − 1 p j − k . . . p j − 1 p_0p_1...p_k = p_{j - k - 1}p_{j - k}...p_{j - 1} p0p1...pk=pj−k−1pj−k...pj−1
的最大整数 k k k,若找到,便是从模式串中的 k + 1 k + 1 k+1号位置开始与目标串T中上一轮不等位置对齐比较,所以取 k + 1 k + 1 k+1。 -
当 j > 1 j > 1 j>1时,不等式 0 ≤ k < j − 1 0 \leq k < j - 1 0≤k<j−1不是矛盾的,这时便是按之前考察的情况,找出满足 p 0 p 1 . . . p k = p j − k − 1 p j − k . . . p j − 1 p_0p_1...p_k = p_{j - k - 1}p_{j - k}...p_{j - 1} p0p1...pk=pj−k−1pj−k...pj−1
的最大整数 k k k,若找不到,便是从 s + 1 s + 1 s+1到 s + j − 1 s + j -1 s+j−1这些轮都不可能匹配,都可以直接忽略,所以应该从目标串T的 s + j s + j s+j位置开始,与模式串P对齐比较,也就是与目标串T上一轮不等位置 s + j s + j s+j对齐的模式串P中的位置应该是0,所以取0。 -
由函数定义可知当 j > 0 j > 0 j>0时, − 1 < n e x t ( j ) < j -1 < next(j) < j −1<next(j)<j,因此,如果 j > 0 j > 0 j>0,那么 n e x t ( j ) next(j) next(j), n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)), n e x t ( n e x t ( n e x t ( j ) ) ) next(next(next(j))) next(next(next(j))),…,这些值必然是递减的整数序列,最终必然会取到0,然后取到-1,到-1后输入参数便不再合法。
-
n e x t ( j + 1 ) ≤ n e x t ( j ) + 1 next(j + 1) \leq next(j) + 1 next(j+1)≤next(j)+1,下面来推导:
-
如果 j = 0 j = 0 j=0, 则 n e x t ( j ) = − 1 next(j) = -1 next(j)=−1,则 n e x t ( j + 1 ) = n e x t ( 1 ) = 0 = − 1 + 1 = n e x t ( j ) + 1 next(j + 1) = next(1) = 0 = -1 + 1 = next(j) + 1 next(j+1)=next(1)=0=−1+1=next(j)+1,所以上述公式成立。
-
如果 j = 1 j = 1 j=1, 则 n e x t ( j ) = 0 next(j) = 0 next(j)=0,则 n e x t ( j + 1 ) = n e x t ( 2 ) ≤ 1 = 0 + 1 = n e x t ( j ) + 1 next(j + 1) = next(2) \leq 1 = 0 + 1 = next(j) + 1 next(j+1)=next(2)≤1=0+1=next(j)+1,所以上述公式成立。
-
如果 j > 1 j > 1 j>1,这时
-
如果 n e x t ( j ) = 0 next(j) = 0 next(j)=0,这时假设 n e x t ( j + 1 ) > n e x t ( j ) + 1 = 1 next(j + 1) > next(j) + 1 = 1 next(j+1)>next(j)+1=1,于是有整数 k > 0 k > 0 k>0使得
p 0 p 1 . . . p k − 1 p k = p j − k p j − k + 1 . . . p j − 1 p j p_0p_1...p_{k-1}p_k = p_{j - k}p_{j - k + 1}...p_{j-1}p_j p0p1...pk−1pk=pj−kpj−k+1...pj−1pj
从而有
p 0 p 1 . . . p k − 1 = p j − k p j − k + 1 . . . p j − 1 p_0p_1...p_{k-1} = p_{j - k}p_{j - k + 1}...p_{j-1} p0p1...pk−1=pj−kpj−k+1...pj−1
即有 n e x t ( j ) = ( k − 1 ) + 1 = k > 0 next(j) = (k - 1) + 1 = k > 0 next(j)=(k−1)+1=k>0,这与假设 n e x t ( j ) = 0 next(j) = 0 next(j)=0矛盾,所以 n e x t ( j + 1 ) > n e x t ( j ) + 1 next(j + 1) > next(j) + 1 next(j+1)>next(j)+1不成立,也就是有 n e x t ( j + 1 ) ≤ n e x t ( j ) + 1 next(j + 1) \leq next(j) + 1 next(j+1)≤next(j)+1。 -
如果 n e x t ( j ) > 0 next(j) > 0 next(j)>0,这是可以假设 n e x t ( j ) = k + 1 next(j) = k + 1 next(j)=k+1,其中 k ≥ 0 k \geq 0 k≥0,这时如果 n e x t ( j + 1 ) = k ′ + 1 > n e x t ( j ) + 1 = ( k + 1 ) + 1 next(j + 1) = k' + 1 > next(j) + 1 = (k + 1) + 1 next(j+1)=k′+1>next(j)+1=(k+1)+1,于是便有整数 k ′ > k + 1 k' > k + 1 k′>k+1使得
p 0 p 1 . . . p k ′ − 1 p k ′ = p j − k ′ p j − k ′ + 1 . . . p j − 1 p j p_0p_1...p_{k'-1}p_{k'} = p_{j - k'}p_{j - k' + 1}...p_{j-1}p_j p0p1...pk′−1pk′=pj−k′pj−k′+1...pj−1pj
从而有
p 0 p 1 . . . p k ′ − 1 = p j − k ′ p j − k ′ + 1 . . . p j − 1 p_0p_1...p_{k'-1} = p_{j - k'}p_{j - k' + 1}...p_{j-1} p0p1...pk′−1=pj−k′pj−k′+1...pj−1
即有 n e x t ( j ) = ( k ′ − 1 ) + 1 = k ′ > k + 1 next(j) = (k' - 1) + 1 = k' > k + 1 next(j)=(k′−1)+1=k′>k+1,这与假设 n e x t ( j ) = k + 1 next(j) = k + 1 next(j)=k+1矛盾,所以 n e x t ( j + 1 ) > n e x t ( j ) + 1 next(j + 1) > next(j) + 1 next(j+1)>next(j)+1不成立,也就是有 n e x t ( j + 1 ) ≤ n e x t ( j ) + 1 next(j + 1) \leq next(j) + 1 next(j+1)≤next(j)+1。
-
-
2.2 next函数实现分析
n e x t next next函数实现是指为模式串P生成函数 n e x t ( j ) next(j) next(j)的所有对应关系,也就是生成整个对应关系表。
对于 n e x t next next函数,存在这样的一个事实,就是 j + 1 j + 1 j+1的函数值 n e x t ( j + 1 ) next(j + 1) next(j+1)可以通过对 j j j的函数值 n e x t ( j ) next(j) next(j)进行多轮 n e x t next next求值而得出。下面来推导这个事实。
首先,如果
j
=
0
j = 0
j=0,有
n
e
x
t
(
j
)
=
−
1
next(j) = -1
next(j)=−1,那么便有
n
e
x
t
(
j
+
1
)
=
n
e
x
t
(
1
)
=
0
=
−
1
+
1
=
n
e
x
t
(
j
)
+
1
next(j + 1) = next(1) = 0 = -1 + 1 = next(j) + 1
next(j+1)=next(1)=0=−1+1=next(j)+1
这儿注意 n e x t ( j ) = − 1 next(j) = -1 next(j)=−1。
其次,如果
j
=
1
j = 1
j=1,有
n
e
x
t
(
j
)
=
0
next(j) = 0
next(j)=0,这时分两种情况,一种是
p
0
=
p
j
p_0 = p_j
p0=pj,也就是
p
0
=
p
1
p_0 = p_1
p0=p1,所以
n
e
x
t
(
j
+
1
)
=
n
e
x
t
(
2
)
=
0
+
1
=
n
e
x
t
(
j
)
+
1
next(j + 1) = next(2) = 0 + 1 = next(j) + 1
next(j+1)=next(2)=0+1=next(j)+1
这儿注意
p
n
e
x
t
(
j
)
=
p
j
p_{next(j)} = p_j
pnext(j)=pj。另一个情况是
p
0
≠
p
j
p_0 \not = p_j
p0=pj,也就是
p
0
≠
p
1
p_0 \not = p_1
p0=p1,所以
n
e
x
t
(
j
+
1
)
=
n
e
x
t
(
2
)
=
0
=
−
1
+
1
=
n
e
x
t
(
0
)
+
1
=
n
e
x
t
(
n
e
x
t
(
j
)
)
+
1
next(j + 1) = next(2) = 0 = -1 + 1 = next(0) + 1 = next(next(j)) + 1
next(j+1)=next(2)=0=−1+1=next(0)+1=next(next(j))+1
这里注意 n e x t ( n e x t ( j ) ) = − 1 next(next(j)) = -1 next(next(j))=−1且 p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj。
最后,我们来看看 j > 1 j > 1 j>1时, n e x t ( j + 1 ) next(j + 1) next(j+1)与 n e x t ( j ) next(j) next(j)的关系,这里先指明,由于 j > 1 j > 1 j>1, 所以必然有 n e x t ( j ) > − 1 next(j) > -1 next(j)>−1, n e x t ( j + 1 ) > − 1 next(j + 1) > -1 next(j+1)>−1:
-
如果 n e x t ( j ) = 0 next(j) = 0 next(j)=0,这时必有 p 0 ≠ p j − 1 p_0 \not = p_{j-1} p0=pj−1,因为若不是这样,必有 n e x t ( j ) > 0 next(j) > 0 next(j)>0,这与假设矛盾。从而有 p 0 p 1 ≠ p j − 1 p j p_0p_1 \not = p_{j - 1}p_j p0p1=pj−1pj。
-
现在,如果 p 0 = p j p_0 = p_j p0=pj,则
n e x t ( j + 1 ) = 0 + 1 = n e x t ( j ) + 1 next(j + 1) = 0 + 1 = next(j) + 1 next(j+1)=0+1=next(j)+1
这儿注意 p n e x t ( j ) = p j p_{next(j)} = p_j pnext(j)=pj。 -
如果 p 0 ≠ p j p_0 \not = p_j p0=pj,必有 n e x t ( j + 1 ) ≠ 1 next(j + 1) \not = 1 next(j+1)=1,又由于 − 1 < n e x t ( j + 1 ) ≤ n e x t ( j ) + 1 = 0 + 1 = 1 -1 < next(j + 1) \leq next(j) + 1 = 0 + 1 = 1 −1<next(j+1)≤next(j)+1=0+1=1,所以
n e x t ( j + 1 ) = 0 = − 1 + 1 = n e x t ( 0 ) + 1 = n e x t ( n e x t ( j ) ) + 1 next(j + 1) = 0 = -1 + 1 = next(0) + 1 = next(next(j)) + 1 next(j+1)=0=−1+1=next(0)+1=next(next(j))+1
这里注意 n e x t ( n e x t ( j ) ) = − 1 next(next(j)) = -1 next(next(j))=−1且 p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj。
-
-
如果 n e x t ( j ) = 1 next(j) = 1 next(j)=1,则有 p 0 = p j − 1 p_0 = p_{j - 1} p0=pj−1且 p 0 p 1 ≠ p j − 2 p j − 1 p_0p_1 \not = p_{j - 2}p_{j - 1} p0p1=pj−2pj−1,由于 − 1 < n e x t ( j + 1 ) ≤ n e x t ( j ) + 1 = 1 + 1 = 2 -1 < next(j + 1) \leq next(j) + 1 = 1 + 1 = 2 −1<next(j+1)≤next(j)+1=1+1=2,所以 n e x t ( j + 1 ) next(j + 1) next(j+1)可能的取值有0、1和2。
-
如果 n e x t ( j + 1 ) = 0 next(j + 1) = 0 next(j+1)=0,则至少有 p 0 ≠ p j p_0 \not = p_j p0=pj, p 0 p 1 ≠ p j − 1 p j p_0p_1 \not = p_{j - 1}p_j p0p1=pj−1pj,而 p 0 = p j − 1 p_0 = p_{j - 1} p0=pj−1,所以 p 1 ≠ p j p_1 \not = p_j p1=pj,另外 n e x t ( j + 1 ) next(j + 1) next(j+1)可如下表示
n e x t ( j + 1 ) = 0 = − 1 + 1 = n e x t ( 0 ) + 1 = n e x t ( n e x t ( 1 ) ) + 1 = n e x t ( n e x t ( n e x t ( j ) ) ) + 1 next(j + 1) = 0 = -1 + 1 = next(0) + 1 = next(next(1)) + 1 = next(next(next(j))) + 1 next(j+1)=0=−1+1=next(0)+1=next(next(1))+1=next(next(next(j)))+1
这儿注意 n e x t ( n e x t ( n e x t ( j ) ) ) = − 1 next(next(next(j))) = -1 next(next(next(j)))=−1, p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj,且 p n e x t ( n e x t ( j ) ) ≠ p j p_{next(next(j))} \not = p_j pnext(next(j))=pj。 -
如果 n e x t ( j + 1 ) = 1 next(j + 1) = 1 next(j+1)=1,则有 p 0 = p j p_0 = p_j p0=pj, p 0 p 1 ≠ p j − 1 p j p_0p_1 \not = p_{j - 1}p_j p0p1=pj−1pj,由于 p 0 = p j − 1 p_0 = p_{j - 1} p0=pj−1,所以 p 1 ≠ p j p_1 \not = p_j p1=pj,另外, n e x t ( j + 1 ) next(j + 1) next(j+1)可如下表示
n e x t ( j + 1 ) = 1 = 0 + 1 = n e x t ( 1 ) + 1 = n e x t ( n e x t ( j ) ) + 1 next(j + 1) = 1 = 0 + 1 = next(1) + 1 = next(next(j)) + 1 next(j+1)=1=0+1=next(1)+1=next(next(j))+1
这儿注意 p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj,且 p n e x t ( n e x t ( j ) ) = p j p_{next(next(j))} = p_j pnext(next(j))=pj。 -
如果 n e x t ( j + 1 ) = 2 next(j + 1) = 2 next(j+1)=2,则有 p 1 = p j p_1 = p_j p1=pj,另外, n e x t ( j + 1 ) next(j + 1) next(j+1)可如下表示
n e x t ( j + 1 ) = 2 = 1 + 1 = n e x t ( j ) + 1 next(j + 1) = 2 = 1 + 1 = next(j) + 1 next(j+1)=2=1+1=next(j)+1
这儿注意 p n e x t ( j ) = p j p_{next(j)} = p_j pnext(j)=pj。
-
-
如果 n e x t ( j ) > 1 next(j) > 1 next(j)>1,可设 n e x t ( j ) = k + 1 next(j)= k + 1 next(j)=k+1,其中, k > 0 k > 0 k>0,则同时存在下面的关系式
0 < k < j − 1 ( 12 ) 0 < k < j - 1 \space \space \color{red}{(12)} 0<k<j−1 (12)
p 0 p 1 . . . p k = p j − k − 1 p j − k . . . p j − 1 ( 13 ) p_0p_1...p_k = p_{j - k - 1}p_{j - k}...p_{j - 1} \space \space \color{red}{(13)} p0p1...pk=pj−k−1pj−k...pj−1 (13)
p 0 p 1 p 2 . . . p k p k + 1 ≠ p j − k − 2 p j − k − 1 p j − k . . . p j − 2 p j − 1 ( 14 ) p_0p_1p_2...p_kp_{k + 1} \not = p_{j - k - 2}p_{j - k - 1}p_{j - k}...p_{j - 2}p_{j - 1} \space \space \color{red}{(14)} p0p1p2...pkpk+1=pj−k−2pj−k−1pj−k...pj−2pj−1 (14)-
现在,如果有 p k + 1 = p j p_{k + 1} = p_j pk+1=pj,则有
p 0 p 1 . . . p k p k + 1 = p j − k − 1 p j − k . . . p j − 1 p j p_0p_1...p_kp_{k + 1} = p_{j - k - 1}p_{j - k}...p_{j - 1}p_j p0p1...pkpk+1=pj−k−1pj−k...pj−1pj
又由 ( 14 ) (14) (14)式可以得到
p 0 p 1 p 2 . . . p k p k + 1 p k + 2 ≠ p j − k − 2 p j − k − 1 p j − k . . . p j − 2 p j − 1 p j p_0p_1p_2...p_kp_{k + 1}p_{k + 2} \not = p_{j - k - 2}p_{j - k - 1}p_{j - k}...p_{j - 2}p_{j - 1}p_j p0p1p2...pkpk+1pk+2=pj−k−2pj−k−1pj−k...pj−2pj−1pj
从而可知
n e x t ( j + 1 ) = ( k + 1 ) + 1 = n e x t ( j ) + 1 next(j + 1) = (k + 1) + 1 = next(j) + 1 next(j+1)=(k+1)+1=next(j)+1
这儿注意 p n e x t ( j ) = p j p_{next(j)} = p_j pnext(j)=pj。 -
而如果 p k + 1 ≠ p j p_{k + 1} \not = p_j pk+1=pj,则必有 n e x t ( j + 1 ) ≠ ( k + 1 ) + 1 next(j + 1) \not = (k + 1) + 1 next(j+1)=(k+1)+1,又 n e x t ( j + 1 ) ≤ n e x t ( j ) + 1 = ( k + 1 ) + 1 next(j + 1) \leq next(j) + 1 = (k + 1) + 1 next(j+1)≤next(j)+1=(k+1)+1,所以有 n e x t ( j + 1 ) < ( k + 1 ) + 1 next(j + 1) < (k + 1) + 1 next(j+1)<(k+1)+1。
-
现在,如果 n e x t ( j + 1 ) > 1 next(j + 1) > 1 next(j+1)>1,则可设 n e x t ( j + 1 ) = k 1 + 1 next(j + 1) = k_1 + 1 next(j+1)=k1+1,其中 0 < k 1 < k + 1 0 < k_1 < k + 1 0<k1<k+1,则有
0 < k 1 < k + 1 ( 15 ) 0 < k_1 < k + 1 \space \space \color{red}{(15)} 0<k1<k+1 (15)
p 0 p 1 . . . p k 1 − 1 p k 1 = p j − k 1 p j − k 1 + 1 . . . p j − 1 p j ( 16 ) p_0p_1...p_{k_1 - 1}p_{k_1} = p_{j - k_1}p_{j - k_1 + 1}...p_{j - 1}p_j \space \space \color{red}{(16)} p0p1...pk1−1pk1=pj−k1pj−k1+1...pj−1pj (16)
p 0 p 1 . . . p k 1 p k 1 + 1 ≠ p j − k 1 − 1 p j − k 1 . . . p j − 1 p j ( 17 ) p_0p_1...p_{k_1}p_{k_1 + 1} \not = p_{j - k_1 - 1}p_{j - k_1}...p_{j - 1}p_j \space \space \color{red}{(17)} p0p1...pk1pk1+1=pj−k1−1pj−k1...pj−1pj (17)
由 ( 13 ) (13) (13)式可得
p j − k 1 p j − k 1 + 1 . . . p j − 1 = p k − k 1 + 1 p k − k 1 + 2 . . . p k ( 18 ) p_{j - k_1}p_{j - k_1 + 1}...p_{j - 1} = p_{k - k_1 + 1}p_{k - k_1 + 2}...p_k \space \space \color{red}{(18)} pj−k1pj−k1+1...pj−1=pk−k1+1pk−k1+2...pk (18)
综合 ( 16 ) (16) (16)式和 ( 18 ) (18) (18)式,则同时存在下面的关系式
p 0 p 1 . . . p k 1 − 1 = p j − k 1 p j − k 1 + 1 . . . p j − 1 = p k − k 1 + 1 p k − k 1 + 2 . . . p k p k 1 = p j p_0p_1...p_{k_1 - 1} = p_{j - k_1}p_{j - k_1 + 1}...p_{j - 1} = p_{k - k_1 + 1}p_{k - k_1 + 2}...p_k\\ p_{k_1} = p_j p0p1...pk1−1=pj−k1pj−k1+1...pj−1=pk−k1+1pk−k1+2...pkpk1=pj
即 n e x t ( k + 1 ) = k 1 next(k + 1) = k_1 next(k+1)=k1且 p k 1 = p j p_{k_1} = p_j pk1=pj,从而有
n e x t ( j + 1 ) = k 1 + 1 = n e x t ( k + 1 ) + 1 = n e x t ( n e x t ( j ) ) + 1 next(j + 1) = k_1 + 1 = next(k + 1) + 1 = next(next(j)) + 1 next(j+1)=k1+1=next(k+1)+1=next(next(j))+1
这儿注意 p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj, p n e x t ( n e x t ( j ) ) = p j p_{next(next(j))} = p_j pnext(next(j))=pj。 -
现在,如果 n e x t ( j + 1 ) = 1 next(j + 1) = 1 next(j+1)=1,则有 p 0 = p j p_0 = p_j p0=pj。则根据 n e x t next next函数的性质,可将 n e x t ( j + 1 ) next(j + 1) next(j+1)表示为
n e x t ( j + 1 ) = 1 = 0 + 1 = n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) + 1 next(j + 1) = 1 = 0 + 1 = next(...next(next(j))...) + 1 next(j+1)=1=0+1=next(...next(next(j))...)+1
其中, n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) next(...next(next(j))...) next(...next(next(j))...)是一直迭代调用到正好结果为0时止,此时 p n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) = p j p_{next(...next(next(j))...)} = p_j pnext(...next(next(j))...)=pj。由 n e x t next next函数性质可知,迭代到取值为0前的那些值,都是大于0的,现在需要考察对于取值大于0的 n e x t ( j ) next(j) next(j), n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)),…这些值,其对应的 p n e x t ( j ) p_{next(j)} pnext(j), p n e x t ( n e x t ( j ) ) p_{next(next(j))} pnext(next(j)),…这些是否与 p j p_j pj相等。-
对于 n e x t ( j ) next(j) next(j),由于 n e x t ( j ) = k + 1 next(j) = k + 1 next(j)=k+1,而已知 p k + 1 ≠ p j p_{k + 1} \not = p_j pk+1=pj,所以 p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj。
-
对于 n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)),由于 n e x t ( n e x t ( j ) ) > 0 next(next(j)) > 0 next(next(j))>0,可设 n e x t ( n e x t ( j ) ) = k 1 + 1 next(next(j)) = k_1 + 1 next(next(j))=k1+1,其中 k 1 ≥ 0 k_1 \geq 0 k1≥0,也就是 n e x t ( k + 1 ) = k 1 + 1 next(k + 1) = k_1 + 1 next(k+1)=k1+1,由于 n e x t ( k + 1 ) < k + 1 next(k + 1) < k + 1 next(k+1)<k+1,所以 k 1 < k k_1 < k k1<k,结合 n e x t ( j ) = k + 1 next(j) = k + 1 next(j)=k+1,则有
p 0 p 1 . . . p k 1 = p k − k 1 p p − k 1 + 1 . . . p k = p j − k 1 − 1 p j − k 1 . . . p j − 1 p_0p_1...p_{k_1} = p_{k - k_1}p_{p - k_1 + 1}...p_k = p_{j - k_1 - 1}p_{j - k_1}...p_{j - 1} p0p1...pk1=pk−k1pp−k1+1...pk=pj−k1−1pj−k1...pj−1
此时如果 p k 1 + 1 = p j p_{k_1 + 1} = p_j pk1+1=pj,则有 n e x t ( j + 1 ) = ( k 1 + 1 ) + 1 ≥ 2 next(j + 1) = (k_1 + 1) + 1 \geq 2 next(j+1)=(k1+1)+1≥2,这与假设 n e x t ( j + 1 ) = 1 next(j + 1) = 1 next(j+1)=1矛盾,所以 p k 1 + 1 ≠ p j p_{k_1 + 1} \not = p_j pk1+1=pj, 也就是 p n e x t ( n e x t ( j ) ) ≠ p j p_{next(next(j))} \not = p_j pnext(next(j))=pj。 -
用与上面同样的方法,可以证明对于取值大于0的 n e x t ( j ) next(j) next(j), n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)),…这些值,其对应的 p n e x t ( j ) p_{next(j)} pnext(j), p n e x t ( n e x t ( j ) ) p_{next(next(j))} pnext(next(j)),…这些与 p j p_j pj都不相等。
-
-
现在,如果 n e x t ( j + 1 ) = 0 next(j + 1) = 0 next(j+1)=0,至少有 p 0 ≠ p j p_0 \not = p_j p0=pj。根据 n e x t next next函数的性质,可将 n e x t ( j + 1 ) next(j + 1) next(j+1)表示为
n e x t ( j + 1 ) = 0 = − 1 + 1 = n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) + 1 next(j + 1) = 0 = -1 + 1 = next(...next(next(j))...) + 1 next(j+1)=0=−1+1=next(...next(next(j))...)+1
其中, n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) next(...next(next(j))...) next(...next(next(j))...)是一直迭代调用到正好结果为-1时止。由 n e x t next next函数性质可知,迭代到结果为-1前的一次迭代结果是0,此时正好有 n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) = 0 next(...next(next(j))...) = 0 next(...next(next(j))...)=0,由于 p 0 ≠ p j p_0 \not = p_j p0=pj,所以这时 p n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) ≠ p j p_{next(...next(next(j))...)} \not = p_j pnext(...next(next(j))...)=pj。而对于迭代到取值为0前的那些值,都是大于0的,现在需要考察对于取值大于0的 n e x t ( j ) next(j) next(j), n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)),…这些值,其对应的 p n e x t ( j ) p_{next(j)} pnext(j), p n e x t ( n e x t ( j ) ) p_{next(next(j))} pnext(next(j)),…这些是否与 p j p_j pj相等。-
对于 n e x t ( j ) next(j) next(j),由于 n e x t ( j ) = k + 1 next(j) = k + 1 next(j)=k+1,而已知 p k + 1 ≠ p j p_{k + 1} \not = p_j pk+1=pj,所以 p n e x t ( j ) ≠ p j p_{next(j)} \not = p_j pnext(j)=pj。
-
对于 n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)),由于 n e x t ( n e x t ( j ) ) > 0 next(next(j)) > 0 next(next(j))>0,可设 n e x t ( n e x t ( j ) ) = k 1 + 1 next(next(j)) = k_1 + 1 next(next(j))=k1+1,其中 k 1 ≥ 0 k_1 \geq 0 k1≥0,也就是 n e x t ( k + 1 ) = k 1 + 1 next(k + 1) = k_1 + 1 next(k+1)=k1+1,由于 n e x t ( k + 1 ) < k + 1 next(k + 1) < k + 1 next(k+1)<k+1,所以 k 1 < k k_1 < k k1<k,结合 n e x t ( j ) = k + 1 next(j) = k + 1 next(j)=k+1,则有
p 0 p 1 . . . p k 1 = p k − k 1 p p − k 1 + 1 . . . p k = p j − k 1 − 1 p j − k 1 . . . p j − 1 p_0p_1...p_{k_1} = p_{k - k_1}p_{p - k_1 + 1}...p_k = p_{j - k_1 - 1}p_{j - k_1}...p_{j - 1} p0p1...pk1=pk−k1pp−k1+1...pk=pj−k1−1pj−k1...pj−1
此时如果 p k 1 + 1 = p j p_{k_1 + 1} = p_j pk1+1=pj,则有 n e x t ( j + 1 ) = ( k 1 + 1 ) + 1 ≥ 2 next(j + 1) = (k_1 + 1) + 1 \geq 2 next(j+1)=(k1+1)+1≥2,这与假设 n e x t ( j + 1 ) = 0 next(j + 1) = 0 next(j+1)=0矛盾,所以 p k 1 + 1 ≠ p j p_{k_1 + 1} \not = p_j pk1+1=pj, 也就是 p n e x t ( n e x t ( j ) ) ≠ p j p_{next(next(j))} \not = p_j pnext(next(j))=pj。 -
用与上面同样的方法,可以证明对于取值大于0的 n e x t ( j ) next(j) next(j), n e x t ( n e x t ( j ) ) next(next(j)) next(next(j)),…这些值,其对应的 p n e x t ( j ) p_{next(j)} pnext(j), p n e x t ( n e x t ( j ) ) p_{next(next(j))} pnext(next(j)),…这些与 p j p_j pj都不相等。
-
-
-
2.3 next函数实现代码(C语言)
综合上部分的分析,归纳实现 n e x t next next函数的算法如下:
-
由于 n e x t ( j + 1 ) next(j + 1) next(j+1)可以依赖 n e x t ( j ) next(j) next(j)而得出,所以应该按 j j j从小到大顺序来求 n e x t ( j ) next(j) next(j), n e x t ( 0 ) = − 1 next(0) = -1 next(0)=−1这个初始项直接给出。
-
由上面分析可知,所有情况下,通行的公式是
n e x t ( j + 1 ) = n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) + 1 next(j + 1) = next(...next(next(j))...) +1 next(j+1)=next(...next(next(j))...)+1
其中,变化的部分只是 n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) next(...next(next(j))...) next(...next(next(j))...),这部分是在上一轮求得的 n e x t ( j ) next(j) next(j)值的基础上,进行0次或者多次迭代 n e x t next next函数求值而得到。其迭代终止条件是 n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) = − 1 next(...next(next(j))...) = -1 next(...next(next(j))...)=−1,或者 p n e x t ( . . . n e x t ( n e x t ( j ) ) . . . ) = p j p_{next(...next(next(j))...)} = p_j pnext(...next(next(j))...)=pj。
next函数实现的C语言代码如下,略去了参数校验,仅供参考:
void kmp_next(const char *p, int p_length, int next_table[]) {
int j = 0;
int next_j = -1;
next_table[j] = next_j;
while (j < p_length - 1) {
if (-1 == next_j || p[j] == p[next_j]) {
++j;
++next_j;
next_table[j] = next_j;
} else {
next_j = next_table[next_j];
}
}
}
3. 基于next跳转表的KMP查找函数实现(C语言)
综合本文,可以归纳出实现KMP字符串查找的算法如下:
-
初始化两个表示字符位置变量,分别对准目标串T的0号位置与模式串P的0号位置。
-
比较两个位置对应的字符,如果相等,则将两个表示字符位置的变量都加1。如果不等,则需要用 n e x t next next函数更新表示模式串P的字符位置的变量的值,而表示目标串T的字符位置的变量则保持不变。
-
如果更新后的表示模式串P的字符位置的变量的值为-1,则需要把两个表示字符位置的变量都加1,然后再进行比较。如果更新后的表示模式串P的字符位置的变量的值不为-1,则可直接比较。
-
重复2和3的步骤,直到这两个表示字符位置变量的值任何一个超出了对应字符串的有效字符位置范围上限。如果表示模式串P的字符位置的变量的值超出了上限,则表示匹配成功,否则表示匹配失败。
KMP字符串查找算法实现的C语言代码如下,略去了参数校验,仅供参考:
int kmp_find(const char *t, int t_length, const char *p, int p_length, int next_table[]) {
int t_pos = 0;
int p_pos = 0;
if (p_length > t_length) return -1;
while (t_pos < t_length && p_pos < p_length) {
if (-1 == p_pos || p[p_pos] == t[t_pos]) {
++p_pos;
++t_pos;
} else {
p_pos = next_table[p_pos];
}
}
if (p_pos < p_length) {
return -1;
} else {
return t_pos - p_length;
}
}
四、完整的KMP字符串模式匹配算法实现及测试(C语言)
一个简单的的测试KMP字符串查找算法的C语言代码及运行结果如下,仅供参考:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void kmp_next(const char *p, int p_length, int next_table[]) {
int j = 0;
int next_j = -1;
next_table[j] = next_j;
while (j < p_length - 1) {
if (-1 == next_j || p[j] == p[next_j]) {
++j;
++next_j;
next_table[j] = next_j;
} else {
next_j = next_table[next_j];
}
}
}
int kmp_find(const char *t, int t_length, const char *p, int p_length, int next_table[]) {
int t_pos = 0;
int p_pos = 0;
if (p_length > t_length) return -1;
while (t_pos < t_length && p_pos < p_length) {
if (-1 == p_pos || p[p_pos] == t[t_pos]) {
++p_pos;
++t_pos;
} else {
p_pos = next_table[p_pos];
}
}
if (p_pos < p_length) {
return -1;
} else {
return t_pos - p_length;
}
}
int find(const char *t, const char *p) {
if (NULL == t || NULL == p) return -1;
int t_length = strlen(t);
int p_length = strlen(p);
if (0 == t_length || 0 == p_length) return -1;
int *next_table = (int *)malloc(p_length * sizeof(int));
if (NULL == next_table) return -1;
kmp_next(p, p_length, next_table);
int pos = kmp_find(t, t_length, p, p_length, next_table);
free(next_table);
return pos;
}
int main(int argc, const char *argv[]) {
int pos = find("abcdefghijklmn", "def");
printf("find \"def\" from \"abcdefghijklmn\": %d\n", pos);
pos = find("abcdefghijklmn", "ddd");
printf("find \"ddd\" from \"abcdefghijklmn\": %d\n", pos);
pos = find("abcdefghijklmn", "abcdefghijklmn");
printf("find \"abcdefghijklmn\" from \"abcdefghijklmn\": %d\n", pos);
pos = find("abcdefghijklmn", "c");
printf("find \"c\" from \"abcdefghijklmn\": %d\n", pos);
return 0;
}
find "def" from "abcdefghijklmn": 3
find "ddd" from "abcdefghijklmn": -1
find "abcdefghijklmn" from "abcdefghijklmn": 0
find "c" from "abcdefghijklmn": 2
Program ended with exit code: 0