kmp算法详解Word下载.docx
- 文档编号:22656842
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:37
- 大小:721.61KB
kmp算法详解Word下载.docx
《kmp算法详解Word下载.docx》由会员分享,可在线阅读,更多相关《kmp算法详解Word下载.docx(37页珍藏版)》请在冰豆网上搜索。
如果失配,即S[i+j]!
=T[j],需要让i++,并且j=0,即每次匹配失败的情况下,模式串T相对于原始串S向右移动了一位。
而KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;
如果匹配失败,即S[i]!
=T[j],需要保持i不变,并且让j=next[j],这里next[j]<
=j-1,即模式串T相对于原始串S向右移动了至少1位(移动的实际位数j-next[j]
>
=1),
同时移动之后,i之前的部分(即S[i-j+1~i-1]),和j=next[j]之前的部分(即T[0~j-2])仍然相等。
显然,相对于BF算法来说,KMP移动更多的位数,起到了一个加速的作用!
(失配的特殊情形,令j=next[j]导致j==0的时候,需要将i++,否则此时没有移动模式串)。
3、BF算法为什么要回溯
首先说一下为什么BF算法要回溯。
如下两字符串匹配(恰如上面所述:
BF算法中,如果当前字符匹配成功,即s[i+j]==T[j],令j++,继续匹配下一个字符):
i+j(j随T中的j++变,而动)
S:
aaaacefghij
j++
T:
aaac
如果不回溯的话就是从下一位开始比起:
aaac
看到上面红颜色的没,如果不回溯的话,那么从a
的下一位c
比起。
然而下述这种情况就漏了(正确的做法当然是要回溯:
=T[j],需要让i++,并且j=0):
所以,BF算法要回溯,其代码如下:
viewplain
1.int
Index(SString
S,
SString
T,
int
pos)
{
2.
//返回T在S中第pos个字符之后的位置
3.
i=pos;
j=1;
k=0;
4.
while
(
i<
=
S[0]
&
j<
T[0]
)
5.
if
(S[i+k]
T[j]
{++k;
++j;
}
//继续比较后续字符
6.
else
{i=i+1;
//指针回溯到
下一首位,重新开始
7.
8.
if(j>
T[0])
return
i;
//子串结束,说明匹配成功
9.
0;
10.}//Index
不过,也有特殊情况可以不回溯,如下:
abcdefghij(主串)
abcdefg(模式串)
即(模式串)没有相同的才不需要回溯。
4、KMP
算法思想
普通的字符串匹配算法必须要回溯。
但回溯就影响了效率,回溯是由T串本身的性质决定的,是因为T串本身有前后'
部分匹配'
的性质。
像上面所说如果主串为abcdef这样的,大没有回溯的必要。
改进的地方也就是这里,我们从T串本身出发,事先就找准了T自身前后部分匹配的位置,那就可以改进算法。
如果不用回溯,那模式串下一个位置从哪里开始呢?
还是上面那个例子,T(模式串)为ababc,如果c失配,那就可以往前移到aba最后一个a的位置,像这样:
...ababd...
ababc
->
ababc
这样i不用回溯,j跳到前2个位置,继续匹配的过程,这就是KMP算法所在。
这个当T[j]失配后,j
应该往前跳的值就是j的next值,它是由T串本身固有决定的,与S串(主串)无关。
5、next数组的含义
重点来了。
下面解释一下next数组的含义,这个也是KMP算法中比较不好理解的一点。
令原始串为:
S[i],其中0<
=i<
=n;
模式串为:
T[j],其中0<
=j<
=m。
假设目前匹配到如下位置
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1,
Si,Si+1,....,Sn
T0,T1,.....................,Tj-1,
Tj,..........
S和T的绿色部分匹配成功,恰好到Si和Tj的时候失配,如果要保持i不变,同时达到让模式串T相对于原始串S右移的话,可以更新j的值,让Si和新的Tj进行匹配,假设新的j用next[j]表示,即让Si和next[j]匹配,显然新的j值要小于之前的j值,模式串才会是右移的效果,也就是说应该有next[j]<
=j-1。
那新的j值也就是next[j]应该是多少呢?
我们观察如下的匹配:
1)如果模式串右移1位(从简单的思考起,移动一位会怎么样),即next[j]=j-1,即让蓝色的Si和Tj-1匹配(注:
省略号为未匹配部分)
Tj,..........(T的划线部分和S划线部分相等【1】)
T0,T1,.................Tj-2,Tj-1,
.......(移动后的T的划线部分和S的划线部分相等【2】)
根据【1】【2】可以知道当next[j]=j-1,即模式串右移一位的时候,有T[0~j-2]==T[1~j-1],而这两部分恰好是字符串T[0~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度(好好揣摩这两个关键字概念:
前缀、后缀,或者再想想,我的上一篇文章,从Trie树谈到后缀树中,后缀树的概念)。
2)如果模式串右移2位,即next[j]=j-2,即让蓝色的Si和Tj-2匹配
S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1,
T0,T1,T2,.....................,Tj-1,
Tj,..........(T的划线部分和S划线部分相等【3】)
T0,T1,...............,Tj-3,Tj-2,.........(移动后的T的划线部分和S的划线部分相等【4】)
同样根据【3】【4】可以知道当next[j]=j-2,即模式串右移两位的时候,有T[0~j-3]==T[2~j-1]。
而这两部分也恰好是字符串T[0~j-1]的前缀和后缀,也就是说next[j]的值取决于模式串T中j前面部分的前缀和后缀相等部分的长度。
3)依次类推,可以得到如下结论:
当发生失配的情况下,j的新值next[j]取决于模式串中T[0~j-1]中前缀和后缀相等部分的长度,并且next[j]恰好等于这个最大长度。
为此,请再允许我引用上文中的一段原文:
“KMP算法中,如果当前字符匹配成功,即S[i]==T[j],令i++,j++,继续匹配下一个字符;
”
于此,也就不难理解了我的关于KMP算法的第二篇文章之中:
“当匹配到S[i]!
=P[j]的时候有S[i-j…i-1]=P[0…j-1].如果下面用j_next去匹配,则有P[0…j_next-1]=S[i-j_next…i-1]=P[j-j_next…j-1]。
此过程如下图3-1所示。
当匹配到S[i]!
=P[j]时,S[i-j…i-1]=P[0…j-1]:
S:
0…
i-j…i-1
i…
P:
0…
j-1
j…
如果下面用j_next去匹配,则有P[0…j_next-1]=S[i-j_next…i-1]=P[j-j_next…j-1]。
所以在P中有如下匹配关系(获得这个匹配关系的意义是用来求next数组):
j-j_next
.…j-1_
…
0
….j_next-1
…
所以,根据上面两个步骤,推出下一匹配位置j_next:
0…i-j…
i-j_next…
i-1
…
j_next-1
j_next…
图3-1求j-next(最大的值)的三个步骤
下面,我们用变量k来代表求得的j_next的最大值,即k表示这S[i]、P[j]不匹配时P中下一个用来匹配的位置,使得P[0…k-1]=P[j-k…j-1],而我们要尽量找到这个k的最大值。
”。
根据上文的【1】与【2】的匹配情况,可得第二篇文章之中所谓的k=1(如aaaa的形式),根据上文的【3】与【4】的匹配情况,k=2(如abab的形式)。
所以,归根究底,KMP算法的本质便是:
针对待匹配的模式串的特点,判断它是否有重复的字符,从而找到它的前缀与后缀,进而求出相应的Next数组,最终根据Next数组而进行KMP匹配。
接下来,进入本文的第二部分。
第二部分、next数组求法的来龙去脉与KMP算法的源码
本部分引自个人此前的关于KMP算法的第二篇文章:
六之续、由KMP算法谈到BM算法。
前面,我们已经知道即不能让P[j]=P[next[j]]成立成立。
不能再出现上面那样的情况啊!
即不能有这种情况出现:
P[3]=b,而竟也有P[next[3]]=P[1]=b。
正如在第二篇文章中,所提到的那样:
“这里读者理解可能有困难的是因为文中,时而next,时而nextval,把他们的思维搞混乱了。
其实next用于表达数组索引,而nextval专用于表达next数组索引下的具体各值,区别细微。
至于文中说不允许P
=P[next[j]]出现,是因为已经有P
=b与S
匹配败,而P[next
]=P1=b,若再拿P[1]=b去与S
匹配则必败。
”--六之续、由KMP算法谈到BM算法。
又恰恰如上文中所述:
“模式串T相对于原始串S向右移动了至少1位(移动的实际位数j-next[j]
=1)”。
ok,求next数组的get_nextval函数正确代码如下:
1.//代码4-1
2.//修正后的求next数组各值的函数代码
3.void
get_nextval(char
const*
ptrn,
plen,
int*
nextval)
4.{
i
nextval[i]
-1;
j
while(
<
plen-1
10.
if(
==
-1
||
ptrn[i]
ptrn[j]
//循环的if部分
11.
12.
++i;
13.
14.
//修正的地方就发生下面这4行
15.
!
//++i,++j之后,再次判断ptrn[i]与ptrn[j]的关系
16.
j;
//之前的错误解法就在于整个判断只有这一句。
17.
18.
nextval[j];
19.
20.
//循环的else部分
21.
22.
23.}
举个例子,举例说明下上述求next数组的方法。
Sabababc
Pababc
S[4]!
=P[4]
那么下一个和S[4]匹配的位置是k=2(也即P[next[4]])。
此处的k=2也再次佐证了上文第3节开头处关于为了找到下一个匹配的位置时k的求法。
上面的主串与模式串开头4个字符都是“abab”,所以,匹配失效后下一个匹配的位置直接跳两步继续进行匹配。
P
ababc
匹配成功
P的next数组值分别为-10-102
next数组各值怎么求出来的呢?
分以下五步:
1.初始化:
i=0,j=-1,nextval[0]=-1。
由于j==-1,进入上述循环的if部分,++i得i=1,++j得j=0,且ptrn[i]!
=ptrn[j](即a!
=b)),所以得到第二个next值即nextval[1]=0;
;
2.i=1,j=0,进入循环esle部分,j=nextval[j]=nextval[0]=-1;
3.进入循环的if部分,++i,++j,i=2,j=0,因为ptrn[i]=ptrn[j]=a,所以nextval[2]=nextval[0]=-1;
4.i=2,j=0,由于ptrn[i]=ptrn[j],再次进入循环if部分,所以++i=3,++j=1,因为ptrn[i]=ptrn[j]=b,所以nextval[3]=nextval[1]=0;
5.i=3,j=1,由于ptrn[i]=ptrn[j]=b,所以++i=4,++j=2,退出循环。
这样上例中模式串的next数组各值最终应该为:
图4-1正确的next数组各值
next数组求解的具体过程如下:
初始化:
nextval[0]=-1,我们得到第一个next值即-1.
图4-2初始化第一个next值即-1
i=0,j=-1,由于j==-1,进入上述循环的if部分,++i得i=1,++j得j=0,且ptrn[i]!
图4-3第二个next值0
上面我们已经得到,i=1,j=0,由于不满足条件j==-1||ptrn[i]==ptrn[j],所以进入循环的esle部分,得j=nextval[j]=-1;
此时,仍满足循环条件,由于i=1,j=-1,因为j==-1,再次进入循环的if部分,++i得i=2,++j得j=0,由于ptrn[i]==ptrn[j](即ptrn[2]=ptrn[0],也就是说第1个元素和第三个元素都是a),所以进入循环if部分内嵌的else部分,得到nextval[2]=nextval[0]=-1;
图4-4第三个next数组元素值-1
i=2,j=0,由于ptrn[i]==ptrn[j],进入if部分,++i得i=3,++j得j=1,所以ptrn[i]==ptrn[j](ptrn[3]==ptrn[1],也就是说第2个元素和第4个元素都是b),所以进入循环if部分内嵌的else部分,得到nextval[3]=nextval[1]=0;
图4-5第四个数组元素值0
如果你还是没有弄懂上述过程是怎么一回事,请现在拿出一张纸和一支笔出来,一步一步的画下上述过程。
相信我,把图画出来了之后,你一定能明白它的。
然后,我留一个问题给读者,为什么上述的next数组要那么求?
有什么原理么?
提示:
我们从上述字符串abab各字符的next值-10-10,可以看出来,根据求得的next数组值,偷用前缀、后缀的概念,一定可以判断出在abab之中,前缀和后缀相同,即都是ab,反过来,如果一个字符串的前缀和后缀相同,那么根据前缀和后缀依次求得的next各值也是相同的。
∙5、利用求得的next数组各值运用Kmp算法
Ok,next数组各值已经求得,万事俱备,东风也不欠了。
接下来,咱们就要应用求得的next值,应用KMP算法来匹配字符串了。
还记得KMP算法是怎么一回事吗?
容我再次引用下之前的KMP算法的代码,如下:
1.//代码5-1
2.//int
kmp_seach(char
const*,
int,
char
KMP模式匹配函数
3.//输入:
src,
slen主串
4.//输入:
patn,
plen模式串
5.//输入:
nextval
KMP算法中的next函数值数组
6.int
kmp_search(char
slen,
nextval,
7.{
pos;
slen
plen
src[i]
patn[j]
//当匹配失败的时候直接用p[j_next]与s[i]比较,
//下面阐述怎么求这个值,即匹配失效后下一次匹配的位置
23.
24.
>
25.
i-plen;
26.
27.
28.}
我们上面已经求得的next值,如下:
图5-1求得的正确的next数组元素各值
以下是匹配过程,分三步:
第一步:
主串和模式串如下,S[3]与P[3]匹配失败。
图5-2第一步,S[3]与P[3]匹配失败
第二步:
S[3]保持不变,P的下一个匹配位置是P[next[3]],而next[3]=0,所以P[next[3]]=P[0],即P[0]与S[3]匹配。
在P[0]与S[3]处匹配失败。
图5-3第二步,在P[0]与S[3]处匹配失败
第三步:
与上文中第3小节末的情况
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- kmp 算法 详解