模式匹配——KMP算法
算法思路
算法产生的原因
由于Brute-Force算法在模式串对主串匹配的过程中,要不断对主串指针进行回溯,实际上有些情况下主串指针的回溯是不必要的,例如:
当模式串在第四个字符与主串失配时,BF的做法是将主串指针回溯,模式串从头开始和主串的下一个字符处开始匹配。这里可以是注意模式串和主串在失配位置前有相同的字符,此时再从头重新匹配是没有必要的。
因此KMP算法就是为加速匹配有用信息,减少不必要的指针回溯,提高算法效率而产生。
KMP算法是D.E.Knuth、J.H.Morris和V.R.Pratt共同提出的,称之为Knuth-Morris-Pratt算法,简称KMP算法。
算法步骤
1. 从模式串t中提取加速匹配的信息
我们需要模式串 t t t中每一个字符 t j ( 0 ≤ j ≤ m − 1 ) t_j(0\leq j\leq m-1) tj(0≤j≤m−1) 前是否存在一个整数 k ( k < j ) k(k<j) k(k<j),使得 t j t_j tj 前的k个字符 ( t j − k , t t − k + 1 , . . . , t j − 1 ) (t_{j-k},t_{t-k+1},...,t_{j-1}) (tj−k,tt−k+1,...,tj−1)与模式串 t t t开头的 k k k个字符 ( t 0 , t 1 , . . . , t k − 1 ) (t_0,t_1,...,t_{k-1}) (t0,t1,...,tk−1)相等。这样在模式串的某位与主串失配时,可将模式串从该位对应的 t k t_k tk开始,重新与失配处匹配。
用数组
n
e
x
t
[
j
]
next[j]
next[j]表示
t
j
t_j
tj对应的最大
k
k
k值,
n
e
x
t
[
j
]
=
m
a
x
{
k
}
next[j]=max\lbrace k\rbrace
next[j]=max{k}。
n
e
x
t
[
j
]
=
{
−
1
M
A
X
{
k
∣
0
<
k
<
j
且
"
t
0
t
1
.
.
.
t
k
−
1
"
=
"
t
j
−
k
t
j
−
k
+
1
.
.
.
t
j
−
1
"
}
0
next[j]=\left\{ \begin{array}{l} -1\\ MAX\lbrace k|0<k<j且"t_0t_1...t_{k-1}"="t_{j-k}t_{j-k+1}...t_{j-1}"\rbrace \\ 0 \end{array} \right.
next[j]=⎩
⎨
⎧−1MAX{k∣0<k<j且"t0t1...tk−1"="tj−ktj−k+1...tj−1"}0
n
e
x
t
[
j
]
next[j]
next[j]求解过程:
(1)
n
e
x
t
[
0
]
=
−
1
next[0]=-1
next[0]=−1,
n
e
x
t
[
1
]
=
0
next[1]=0
next[1]=0(不考虑
j
j
j或1~
j
−
1
j-1
j−1的位置没有字符的情况);
(2)若
"
t
0
t
1
.
.
.
t
k
−
1
"
=
"
t
j
−
k
t
j
−
k
+
1
.
.
.
t
j
−
1
"
"t_0t_1...t_{k-1}"="t_{j-k}t_{j-k+1}...t_{j-1}"
"t0t1...tk−1"="tj−ktj−k+1...tj−1",有
n
e
x
t
[
j
]
=
k
next[j]=k
next[j]=k;
(3)若
t
j
=
t
k
t_{j}=t_{k}
tj=tk,即
"
t
0
t
1
.
.
.
t
k
"
=
"
t
j
−
k
t
j
−
k
+
1
.
.
.
t
j
−
1
t
j
"
"t_0t_1...t_k"="t_{j-k}t_{j-k+1}...t_{j-1}t_j"
"t0t1...tk"="tj−ktj−k+1...tj−1tj",有
n
e
x
t
[
j
+
1
]
=
k
+
1
next[j+1]=k+1
next[j+1]=k+1;
(4)若
t
j
≠
t
k
t_{j}\neq t_{k}
tj=tk,说明
t
j
+
1
t_{j+1}
tj+1前不存在长度为
n
e
x
t
[
j
]
+
1
next[j]+1
next[j]+1的子串与开头字符起的子串相同,此时将
k
k
k回退到
k
′
=
n
e
x
t
[
k
]
k'=next[k]
k′=next[k],即比较
t
n
e
x
t
[
k
]
(
t
k
′
)
t_{next[k]}(t_{k '})
tnext[k](tk′)与
t
j
t_j
tj是否相等,直到回退到不存在可匹配的子串,则
n
e
x
t
[
j
+
1
]
=
0
next[j+1]=0
next[j+1]=0;若
t
j
≠
t
k
t_{j}\neq t_{k}
tj=tk,置
k
=
n
e
x
t
[
k
]
k=next[k]
k=next[k]。
2. 模式匹配过程
当主串
s
s
s和模式串
t
t
t在对应
t
j
t_j
tj与
s
i
s_i
si位置失配时,根据
n
e
x
t
[
j
]
=
k
next[j]=k
next[j]=k的值,直接将模式串从头和主串
s
i
s_i
si位置的前
k
k
k处对其,直接重新比较
t
k
t_k
tk和
s
i
s_i
si是否相等,当失配移动使得位置取到
n
e
x
t
[
0
]
=
−
1
next[0]=-1
next[0]=−1,表示当前已经没有字符可与
s
i
s_i
si比较,则下一次比较从
s
i
+
1
s_{i+1}
si+1和
t
0
t_0
t0开始比较。
过程描述:
i=0;j=0;
while(s和t都没扫描完)
{ if(就或者当前两个串指向的字符相等)
i和j分别增1;
else
i不变,j回退到j=next[j] (模式串右滑)
}
if(j超界)返回i-t的长度 //模式匹配成功
else 返回 -1 //模式匹配失败
代码演示
模式串的next数组
void GetNext(String t,int next[])//由模式串t求出next数组
{
int j,k;//j扫描模式串t
j=0;k=-1;//k记录t[j]之前与模式串开头起相同子串的字符个数
next[0]=-1;//设置next[0]的值
while(j<t.length-1) //求模式串t所有位置的next值
{
if(k==-1||t.data[j]==t.data[k])
{
k++;j++;
next[j]=k;
}
else k=next[k];
}
}
KMP模式匹配过程
int KMPIndex(String s,String t)//KMP算法(返回匹配位置的首位)
{
int next[MaxSize],i=0;j=0;
GetNext(t,next);
while(i<s.length&&j<t.length)
{
if(j==-1||s.data[i]==t.data[j])
{i++;j++;}
else j=next[j]; //模式串右滑
}
if(j>=t.length)return (i-t.length); //匹配成功
else return (-1); //匹配失败
}
优化
考虑到当 t j t_j tj与 s i s_i si失配时,若 t j = t k t_j=t_k tj=tk,则也没有必要在下一次匹配时将模式串右移 k k k位(将 t k t_k tk与 s i s_i si比较),而是直接将 s i s_i si与 t n e x t [ k ] t_{next[k]} tnext[k]比较,以此类推。由此引入 n e x t v a l [ j ] nextval[j] nextval[j]数组。
n e x t v a l [ j ] nextval[j] nextval[j]的定义是 n e x t v a l [ 0 ] = − 1 nextval[0]=-1 nextval[0]=−1,当 t j = t n e x t [ j ] t_j=t_{next[j]} tj=tnext[j]时, n e x t v a l [ j ] = n e x t v a l [ n e x t [ j ] ] nextval[j]=nextval[next[j]] nextval[j]=nextval[next[j]],否则 n e x t v a l [ j ] = n e x t [ j ] nextval[j]=next[j] nextval[j]=next[j]。
用 n e x t v a l nextval nextval取代 n e x t next next,得到优化后的KMP算法如下:
- 由模式串求出 n e x t v a l nextval nextval值
void GetNextval(String t,int nextval[])//由模式串t求出nextval数组
{
int j,k;//j扫描模式串t
j=0;k=-1;//k记录t[j]之前与模式串开头起相同子串的字符个数
nextval[0]=-1;//设置nextval[0]的值
while(j<t.length-1) //求模式串t所有位置的next值
{
if(k==-1||t.data[j]==t.data[k])
{
k++;j++;
if(t.data[j]!=t.data[k]) //主要修改部分
nextval[j]=k;
else
nextval[j]=nextval[k];
}
else k=nextval[k];
}
}
- 匹配过程
int KMPIndex(String s,String t)//优化后KMP算法(返回匹配位置的首位)
{
int nextval[MaxSize],i=0;j=0;
GetNextval(t,nextval);
while(i<s.length&&j<t.length)
{
if(j==-1||s.data[i]==t.data[j])
{i++;j++;}
else j=nextval[j]; //模式串右滑
}
if(j>=t.length)return (i-t.length); //匹配成功
else return (-1); //匹配失败
}