flag史上最容易懂的KMP解析
如果要学习KMP的正确性请期待以后的博客
KMP概述
KMP算法是由
Knuth/Morris/Pratt
K
n
u
t
h
/
M
o
r
r
i
s
/
P
r
a
t
t
三个人设计的线性字符串匹配算法。
这个算法用到了一个函数“前缀函数”,这里称作
π(i)
π
(
i
)
。
下面我们看几个概念
文本T:是要查询的目标文章,长度记作 T.len T . l e n
模式P:是你的字典词,长度记作
P.len
P
.
l
e
n
也就是我们要在文本里面找模式出现的次数和位置。
P[1..10]代表模式P第一个位置到第十个位置的字符串。
偏移s,如果我们正在比较 T[s+1..s+P.len] T [ s + 1.. s + P . l e n ] 和 P P ,那么第一个字符在中的位置的前一个位置就是偏移s。可见,如果 s=0 s = 0 ,那么我们正在比较 T[1..P.len] T [ 1.. P . l e n ] 和 P P 。
前缀:对于一个字符串,从第一个字符开始的,所有的子串(本身除外)。
例如,对于“abcde”,它的前缀有”a”,”ab”,”abc”,”abcd”。
请注意,”abcde”不算是它的前缀。
后缀:参照前缀的概念,后缀是:对于一个字符串,以最后一个字符结尾的,所有的子串(本身除外)。
例如,对于”abcde”,它的后缀有”e”,”de”,”cde”,”bcde”。
请注意,”abcde”不算是它的后缀。
前缀函数:见下:
前缀函数
这里前缀函数我们记作。这个函数是针对模式
P
P
的。对于串(P的一个从P[1]开始的子串),设它前缀集合为
A
A
,设它的后缀集合是,那么
π(i)
π
(
i
)
就是
A∩B
A
∩
B
中长度最长的字符串的长度。
例如:有一个字符串为
"ababaca"
"
a
b
a
b
a
c
a
"
当
i=5
i
=
5
的时候,
P[1..5]=
P
[
1..5
]
=
"ababa"
"
a
b
a
b
a
"
它的前缀集合:
它的后缀集合:
所以,
我们可以发现在 A∩B A ∩ B 中,长度最长的字符串是 aba a b a ,长度为 3 3 ,所以。
我们可以预处理这个模式的前缀函数:
前缀函数的功能
我们回顾朴素字符串匹配法,我们要枚举所有的偏移 s s ,然后在每个偏移都花上的时间去枚举匹配。总时间复杂度是 O(P.len∗T.len) O ( P . l e n ∗ T . l e n )
前缀函数的用法
如果我们在考虑一个偏移 s s 的时候,前个字符匹配,但是第 k+1 k + 1 个字符不匹配,我们直接跳到偏移 s′=s+π(k) s ′ = s + π ( k ) 继续匹配。
这样跳偏移的作用
这样跳偏移,可以省略中间很多没有可能匹配到的偏移。很显然,我们跳到的偏移肯定是最有可能匹配的偏移。
这样跳的正确性
等待后续博客……
前缀函数的预处理
见下。我们先讲怎么匹配字符串。
字符串匹配
如果我们在考虑一个偏移
s
s
的时候,前个字符匹配,但是第
k+1
k
+
1
个字符不匹配,我们直接跳到偏移
s′=s+π(k)
s
′
=
s
+
π
(
k
)
继续匹配。
下面给出图解:
如果我们在处理偏移
s=4
s
=
4
的时候,匹配了5个字符,第6个字符不匹配:
我们取偏移
s′=s+π(5)=4+3=7
s
′
=
s
+
π
(
5
)
=
4
+
3
=
7
已经匹配的字符个数为
π(5)=3
π
(
5
)
=
3
个字符。
code
const int maxn=1000020;
char T[maxn],P[maxn];//两个字符串
int T_len,P_len;//两个字符串的长度
int pai[maxn];//pi函数
int ans=0;//P在T中出现的个数
void KMP()
{
int q=0;//q记录了有几个字符一起匹配
for(int i=1;i<=T_len;i++)//枚举呗
{
while(q>0&&P[q+1]!=T[i])//如果不匹配,那就默认pai[q]个字符已经
q=pai[q];
if(P[q+1]==T[i])//如果匹配,那就已经匹配的字符个数+1
q++;
if(q==P_len)
ans++,q=pai[q];
//如果已经匹配的个数跟模式的长度相同,那么就已经匹配成功
}
}
预处理 π π 前缀函数
其实预处理 π π 前缀函数的过程,就是模式串自我匹配的过程。
code
自己理解吧!
int pai[maxn];
void get_p()
{
pai[0]=0;
int k=0;
for(int q=2;q<=P_len;q++)
{
while(k>0&&P[k+1]!=P[q])
k=pai[k];
if(P[k+1]==P[q])
k++;
pai[q]=k;
}
}
时间复杂度分析
我们先看字符串匹配的过程。
在最坏的情况下,
while(q>0&&P[q+1]!=T[i])
//如果不匹配,那就默认pai[q]个字符已经匹配
q=pai[q];
这些while语句中,我们可以证明,总共执行次数
≤T.len
≤
T
.
l
e
n
.所以,字符串匹配过程的时间复杂度为
O(T.len)
O
(
T
.
l
e
n
)
同上,预处理时间复杂度为
O(P.len)
O
(
P
.
l
e
n
)
.