KMP
核心
玄学
fail
f
a
i
l
数组
fail
f
a
i
l
数组的含义即是某段字符串的最长公共前后缀。
具体来讲,设
T[1…j−1]=T[i−j…i−1](j⩽i)
T
[
1
…
j
−
1
]
=
T
[
i
−
j
…
i
−
1
]
(
j
⩽
i
)
,那么
fail[i]
f
a
i
l
[
i
]
为
j
j
的最大值。
那么这个 有什么用呢?见下图。
匹配时,设当前匹配到
S
S
的第 位,
T
T
的第 位,即
S[i−j+1…i−1]
S
[
i
−
j
+
1
…
i
−
1
]
与
T[1…j−1]
T
[
1
…
j
−
1
]
已成功匹配。当
j
j
指针这一位发生失配,意味着 ,这时根据
fail
f
a
i
l
数组的定义,由于
T[1…fail[j]−1]=T[j−fail[j]…j−1]
T
[
1
…
f
a
i
l
[
j
]
−
1
]
=
T
[
j
−
f
a
i
l
[
j
]
…
j
−
1
]
,因此若将
j
j
指针指向 ,我们可以直接跳过对
T[1…fail[j]−1]
T
[
1
…
f
a
i
l
[
j
]
−
1
]
的匹配。
复杂度证明
设
n
n
为串 的长度,
m
m
为串 的长度。
i
i
指针全程只增,这里的复杂度为 ;
j
j
指针全程只有两种跳法: 或
j→fail[j]
j
→
f
a
i
l
[
j
]
。
对于
j→j+1
j
→
j
+
1
全程最多跳
n
n
次。
对于 :
在
i
i
保持不变的情况下, 跳至下界的极限次数一定不超过
j
j
(根据 的定义)。因此设
f[j]
f
[
j
]
表示
j
j
这个位置发生失配跳至下界的上限次数, 为跳完
f[j]
f
[
j
]
次之后
j
j
的位置。而 每跳一次,
g[j]
g
[
j
]
一定减小至少
1
1
, 随之减小至少
1
1
,从而最终跳的次数上界为 。由
f
f
的定义我们知道, ,故最终
j
j
跳的次数一定不超过 。
又由于要单独对串
T
T
单独求一次 ,复杂度证明同上,为
O(m)
O
(
m
)
。
综上,由于
i
i
全程迭代 次,
j
j
全程迭代不超过 次,故时间复杂度为
O(n+m)
O
(
n
+
m
)
。
扩展KMP
“扩展”
引入
ext
e
x
t
数组,
ext[i]
e
x
t
[
i
]
表示
S[i…n]
S
[
i
…
n
]
与
T[1…m]
T
[
1
…
m
]
的最长公共前缀(
n
n
为串 的长度,
m
m
为串 的长度)。
考虑如何求
ext
e
x
t
。
引入辅助工具
设当前需要计算
ext[i]
e
x
t
[
i
]
的值,
p
p
为 最大时
j
j
的值 ,
就有
S[p…p+ext[p]−1]=T[1…ext[p]]
S
[
p
…
p
+
e
x
t
[
p
]
−
1
]
=
T
[
1
…
e
x
t
[
p
]
]
,
于是
S[i…p]=T[ext[p]−p+i…ext[p]]
S
[
i
…
p
]
=
T
[
e
x
t
[
p
]
−
p
+
i
…
e
x
t
[
p
]
]
。
这时求
ext
e
x
t
就有两种情况:
1、
i+fail[i]<p+ext[p]
i
+
f
a
i
l
[
i
]
<
p
+
e
x
t
[
p
]
由于
S[i…p]=T[ext[p]−p+i…ext[p]]
S
[
i
…
p
]
=
T
[
e
x
t
[
p
]
−
p
+
i
…
e
x
t
[
p
]
]
,
又由
fail
f
a
i
l
的定义知
S[i…i+fail[i−p+1]−1]=T[1…fail[i−p+1]]
S
[
i
…
i
+
f
a
i
l
[
i
−
p
+
1
]
−
1
]
=
T
[
1
…
f
a
i
l
[
i
−
p
+
1
]
]
,
fail[i−p+1]
f
a
i
l
[
i
−
p
+
1
]
为最大匹配长度,故
ext[i]=fail[i−p+1]
e
x
t
[
i
]
=
f
a
i
l
[
i
−
p
+
1
]
。
2、
i+fail[i]≥p+ext[p]
i
+
f
a
i
l
[
i
]
≥
p
+
e
x
t
[
p
]
此处求法与
fail
f
a
i
l
求法几乎一样,可参考
fail
f
a
i
l
的求值。
复杂度证明:对于情况1,单次复杂度为 O(1) O ( 1 ) ;对于情况2,总复杂度与KMP算法中一致,为 O(n+m) O ( n + m ) 。
代码实现
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int LENGTH=1000000;
char S[LENGTH+2],T[LENGTH+2];
namespace KMP{
int fail[LENGTH+2];
int cnt[LENGTH+2],ext[LENGTH+2];
void get_fail(char *t){
int len=strlen(t+1);
for(int i=2,j=0;i<=len;++i){
while(j&&t[i]!=t[j+1])j=fail[j];
if(t[i]==t[j+1])fail[i]=++j;
}
}
void KMP(char *s,char *t){
get_fail(t);
int s_len=strlen(s+1),t_len=strlen(t+1);
for(int i=1,j=0;i<=s_len;++i){
while(j&&s[i]!=t[j+1])j=fail[j];
if(s[i]==t[j+1]){
cnt[i]=++j;
if(j==t_len)j=fail[j];
}
}
}
void ex_KMP(char *s,char *t){
get_fail(t);
int s_len=strlen(s+1),t_len=strlen(t+1);
int p=1;
while(ext[1]<t_len&&s[ext[1]+1]==t[ext[1]+1])++ext[1];
for(int i=2;i<=s_len;++i){
if(i+fail[i-p+1]<p+ext[p])ext[i]=fail[i-p+1];
else{
int j=ext[p]+p-i;
if(j<0)j=0;
while(i+j<=s_len&&j<t_len&&s[i+j]==t[j+1])++j;
ext[i]=j;
p=i;
}
}
}
}
int main(){
scanf("%s%s",S+1,T+1);
KMP::KMP(S,T);
KMP::ex_KMP(S,T);
int s_len=strlen(S+1),t_len=strlen(T+1);
printf("Array of fail:\n\t");
for(int i=1;i<=t_len;++i)printf("%d ",KMP::fail[i]);
printf("\nMatching position:\n\t");
for(int i=1;i<=s_len;++i)if(KMP::cnt[i]==t_len)printf("%d ",i-t_len+1);
printf("\nArray of extend:\n\t");
for(int i=1;i<=s_len;++i)printf("%d ",KMP::ext[i]);
}