字符串的模式匹配是以下的一种常见问题:给定字符串 s s s(主串)和 t t t(模式串) ( ∣ s ∣ = n , ∣ t ∣ = m , m ≤ n ) (|s|=n,|t|=m,m\leq n) (∣s∣=n,∣t∣=m,m≤n),求 t t t在 s s s中第一次出现的位置。如:
s
=
"
a
b
a
b
c
a
b
c
a
c
b
a
b
"
s="ababcabcacbab"
s="ababcabcacbab"
t
=
"
a
b
c
a
c
"
t=\ \ \ \ \ \ \ \ \ \ "abcac"
t= "abcac"
则
t
t
t首次出现在对齐处,即主串的第6位。很容易想到一种枚举的方法(Brute Force):
1.BF算法(Brute Force)
BF算法即从 i = 1 i=1 i=1开始, i i i至多到 n − m + 1 n-m+1 n−m+1,每次将模式串 t t t的开始与 s s s的第 i i i位对齐并进行对照,即比较 s [ i . . . i + m − 1 ] s[i...i+m-1] s[i...i+m−1]与 t [ 1... m ] t[1...m] t[1...m]是否相等。显然,这种算法的最差复杂度当 t t t每次都在最后一位与 s s s失配时出现,总比较次数 = m ( n − m + 1 ) = O ( n m ) =m(n-m+1)=O(nm) =m(n−m+1)=O(nm).
2.KMP算法
KMP算法通过计算模式串 t t t的一些额外信息 ( n e x t (next (next数组 ) ) )来避免不必要的必定失配的情况,从而优化时间复杂度。它由Knuth、Morris和Pratt同时提出,故得名KMP算法。
现考虑BF算法在匹配失配时的情况。设
s
s
s与
t
t
t在
s
[
i
]
,
t
[
j
]
s[i],t[j]
s[i],t[j]处失配,即:
s
[
i
−
j
+
1...
i
−
1
]
=
t
[
1..
j
−
1
]
s[i-j+1...i-1]=t[1..j-1]
s[i−j+1...i−1]=t[1..j−1]
s
[
i
]
≠
t
[
j
]
s[i]\neq t[j]
s[i]=t[j]
按照BF算法,失配后应将
t
t
t右移一位,使得
s
[
i
−
j
+
2
]
与
t
[
1
]
s[i-j+2]与t[1]
s[i−j+2]与t[1]对齐进行下一次匹配。此时,若对于模式串
t
t
t有
t [ 1... j − 2 ] ≠ t [ 2... j − 1 ] ( 1 ) t[1...j-2] \neq t[2...j-1]\ \ \ \ \ \ \ \ \ \ \ \ \ \ (1) t[1...j−2]=t[2...j−1] (1),
那么就有
t [ 1... j − 2 ] ≠ t [ 2... j − 1 ] = s [ i − j + 2... i − 1 ] t[1...j-2] \neq t[2...j-1] = s[i-j+2...i-1] t[1...j−2]=t[2...j−1]=s[i−j+2...i−1],
因此
t
[
1...
j
−
2
]
≠
s
[
i
−
j
+
2...
i
−
1
]
t[1...j-2] \neq s[i-j+2...i-1]
t[1...j−2]=s[i−j+2...i−1](可参照下图,相同颜色表示串相同)
图中
s
s
s中橙色框一定与
t
2
t_2
t2(下一次匹配)中的绿框不相等(式
(
1
)
(1)
(1)),因此下一次匹配一定会在模式串的
1...
j
−
2
1...j-2
1...j−2处失败
(
∗
)
(*)
(∗)。
同理,当有
t
[
1...
j
−
3
]
≠
t
[
3...
j
−
1
]
t[1...j-3] \neq t[3...j-1]
t[1...j−3]=t[3...j−1]时,下下次匹配一定会失败。那么什么时候匹配有可能成功呢?我们需要找到从大到小第一个出现的值
k
k
k,使得
t
[
1...
k
]
≠
t
[
j
−
k
.
.
.
j
−
1
]
t[1...k] \neq t[j-k...j-1]
t[1...k]=t[j−k...j−1]而
t
[
1...
k
−
1
]
=
t
[
j
−
k
+
1...
j
−
1
]
t[1...k-1]=t[j-k+1...j-1]
t[1...k−1]=t[j−k+1...j−1],即子串
t
[
1...
j
−
1
]
t[1...j-1]
t[1...j−1]的最长的相同的前后缀长度+1。
如:
t
=
"
a
b
a
a
b
c
a
c
"
t="abaabcac"
t="abaabcac"
当
j
=
6
j=6
j=6时,
t
[
1...5
]
t[1...5]
t[1...5]最长相同的前后缀为
"
a
b
"
"ab"
"ab"长为
2
2
2,因此此时
k
k
k值为
3
3
3.
当我们能求出这个
k
k
k的值后,我们就可以推断出下一次可能匹配成功的位置,见下图。
我们已经断定跳过的几次匹配一定会失配,因此我们可以直接将
t
[
1
]
t[1]
t[1]与
s
[
i
−
k
+
1
]
s[i-k+1]
s[i−k+1]对齐,而由相同前后缀的性质,标红框的部分完全相同,因此还可以跳过这一次匹配的
1...
k
−
1
1...k-1
1...k−1部分,直接将
s
[
i
]
s[i]
s[i]与
t
[
k
]
t[k]
t[k]进行比对。
通过上面的分析,我们可以总结出
k
k
k的一些性质:
(
1
)
k
(1)k
(1)k只与模式串
t
t
t自己有关,而且是一个关于下标
j
j
j的函数,即
k
=
n
e
x
t
(
j
)
;
k=next(j);
k=next(j);
(
2
)
0
≤
n
e
x
t
(
j
)
≤
j
−
1
(2)0 \leq next(j) \leq j-1
(2)0≤next(j)≤j−1,它表明
t
[
1...
j
]
t[1...j]
t[1...j]有长为
n
e
x
t
(
j
)
−
1
next(j)-1
next(j)−1的相同前后缀
;
;
;
(
3
)
(3)
(3)当不存在相同前后缀时,
n
e
x
t
(
j
)
=
1
;
next(j)=1;
next(j)=1;
(
4
)
(4)
(4)定义
n
e
x
t
(
1
)
=
0.
next(1)=0.
next(1)=0.
3.next数组的求法
n
e
x
t
next
next数组可通过归纳求得。以下是具体分析:
(
1
)
n
e
x
t
[
1
]
=
0
;
(1)next[1]=0;
(1)next[1]=0;
(
2
)
(2)
(2)假设
n
e
x
t
[
1
]
,
n
e
x
t
[
2
]
,
.
.
.
,
n
e
x
t
[
j
]
next[1],next[2],...,next[j]
next[1],next[2],...,next[j]均已知,现求出
n
e
x
t
[
j
+
1
]
.
next[j+1].
next[j+1].
设
n
e
x
t
[
j
]
=
k
,
next[j]=k,
next[j]=k,这说明
t
[
1...
j
−
1
]
t[1...j-1]
t[1...j−1]有长为
k
−
1
k-1
k−1的相同前后缀。
①当
t
[
k
]
=
t
[
j
]
t[k]=t[j]
t[k]=t[j],那么
t
[
1...
j
]
t[1...j]
t[1...j]便有长为
k
k
k的相同前后缀。那么
t
[
1...
j
]
t[1...j]
t[1...j]能否有比
k
k
k更长的相同前后缀呢?答案是不能。如图:
红框圈起来的部分代表
n
e
x
t
(
j
)
next(j)
next(j)所表示的相同前后缀;紫色框表示
t
[
k
]
=
t
[
j
]
t[k]=t[j]
t[k]=t[j]所带来的
t
[
1...
j
]
t[1...j]
t[1...j]的长为
k
k
k的前后缀。左边的紫框一定不等于绿框,否则
n
e
x
t
(
j
)
next(j)
next(j)便可取到
k
+
1
,
k+1,
k+1,因此两个黄框一定不相等,因为它们前
k
k
k位一个是紫框,一个是绿框。因此当
t
[
k
]
=
t
[
j
]
,
n
e
x
t
(
j
+
1
)
=
k
+
1
=
n
e
x
t
(
j
)
+
1
t[k]=t[j],next(j+1)=k+1=next(j)+1
t[k]=t[j],next(j+1)=k+1=next(j)+1.
②当
t
[
k
]
≠
t
[
j
]
t[k] \neq t[j]
t[k]=t[j],此时视为模式串
t
t
t与自身匹配,而且
t
[
k
]
t[k]
t[k]和
t
[
j
]
t[j]
t[j]失配。由①可知,只能寻找比
k
k
k更短的相同前后缀。
此时将下面的串不断向右移动,判断重叠部分(即绿框)是否相等,找出最长的相同前后缀。由KMP
(
∗
)
(*)
(∗)部分可知,可以直接将下面的串的第
k
′
=
n
e
x
t
[
k
]
k'=next[k]
k′=next[k]与上面串的第
j
j
j位比较(否则一定会失配)。如果移动后
t
[
j
]
=
t
[
k
′
]
,
t[j]=t[k'],
t[j]=t[k′],那么便有
n
e
x
t
(
j
+
1
)
=
k
′
+
1
=
n
e
x
t
(
k
)
+
1
=
n
e
x
t
(
n
e
x
t
(
j
)
)
+
1
;
next(j+1)=k'+1=next(k)+1=next(next(j))+1;
next(j+1)=k′+1=next(k)+1=next(next(j))+1;如若不然,不断令
k
(
n
)
=
n
e
x
t
(
k
(
n
−
1
)
)
k^{(n)}=next(k^{(n-1)})
k(n)=next(k(n−1)),直到:
(
a
)
(a)
(a)
t
[
j
]
=
t
[
k
(
n
)
]
t[j]=t[k^{(n)}]
t[j]=t[k(n)],则
n
e
x
t
(
j
+
1
)
=
k
(
n
)
+
1.
next(j+1)=k^{(n)}+1.
next(j+1)=k(n)+1.
(
b
)
(b)
(b)直至迭代到
k
(
n
)
=
1
k^{(n)}=1
k(n)=1时,
t
[
j
]
≠
t
[
k
(
n
)
]
t[j] \neq t[k^{(n)}]
t[j]=t[k(n)],即上下串仅一位重叠仍不相等(下图),说明没有公共前后缀,此时令
n
e
x
t
(
j
+
1
)
=
1.
next(j+1)=1.
next(j+1)=1.
4.代码实现
-求 n e x t next next数组
void GetNext(char *t,int *next)
{
int i = 1,j = 0;
while(i<m)//m为模式串长度
{
if(j == 0 || t[i] == t[j])
{
i++;
j++;
next[i] = j;
}
else j = next[j];
}
}
代码中
i
i
i表示当前要求的
n
e
x
t
next
next下标,
j
j
j表示下面的串的第
j
j
j位与上面的串(上一部分图解)尝试进行匹配。
j
=
=
0
j == 0
j==0表示当迭代到最后仍然没有公共前后缀的情况,此时
n
e
x
t
[
+
+
i
]
=
1.
next[++i]=1.
next[++i]=1.
t
[
i
]
=
t
[
j
]
t[i]=t[j]
t[i]=t[j]说明当前匹配成功,记录下
n
e
x
t
[
+
+
i
]
=
j
+
1
next[++i]=j+1
next[++i]=j+1,否则迭代
j
j
j为
n
e
x
t
[
j
]
。
next[j]。
next[j]。
-KMP
int KMP(char *s,char *t)
{
int i = 1,j = 1;
while(i<=n && j<=m)
{
if(j == 0 || s[i] == t[j])
{
i++;
j++;
}
else j = next[j];
}
if(j>m) return i-m;
else return 0;
}
与求
n
e
x
t
next
next数组很相似,当
j
=
0
j=0
j=0时,说明
s
[
i
]
s[i]
s[i]与
t
[
1
]
t[1]
t[1]不相等,此时模式串右移一位将
j
j
j置为
1
1
1,同时
i
+
+
i++
i++使得模式串从头开始与主串下一位匹配。
当
s
[
i
]
=
=
t
[
j
]
s[i]==t[j]
s[i]==t[j]时,说明当前位匹配成功,
i
,
j
i,j
i,j均自增。否则
j
j
j迭代为
n
e
x
t
[
j
]
next[j]
next[j].
当
j
>
m
j>m
j>m时,说明模式串匹配完成,匹配成功位置为
(
i
−
1
)
−
m
+
1
=
i
−
m
(i-1)-m+1=i-m
(i−1)−m+1=i−m(由于
i
i
i在匹配成功时自增了
1
1
1,需要减掉)
KMP算法的复杂度为 O ( n + m ) = O ( n ) . O(n+m)=O(n). O(n+m)=O(n).