最近在刷题的时候遇到了KMP 算法的一些题目,回想起来自己关于数据结构和算法的知识内容都已经忘了。于是打算重新复习一遍。就从KMP算法开始吧。因为自己水平还不够,也讲不清楚。于是就从网上转载了两篇博文。
https://blog.csdn.net/v_JULY_v/article/details/6545192
http://blog.csdn.net/v_JULY_v。
作者:滨湖,July、yansha。
说明:初稿由滨湖提供,July负责KMP部分的勘误,yansha负责BM部分的修改。全文由July统稿修订完成。
出处:http://blog.csdn.net/v_JULY_v。
引言
六、教你从头到尾彻底理解KMP算法、updated之后,KMP算法会写一个续集;2、写这个kMP算法的文章很多很多,但真正能把它写明白的少之又少;3、这个KMP算法曾经困扰过我很长一段时间。我也必须让读者真真正正彻彻底底的理解它。希望,我能做到。
模式匹配,.,.,,HTTP,,HTTP.
KMPBMBMBMKMPBM
第一部分、KMP算法
- 1、回溯法字符串匹配算法
- //代码1-1
- //查找出模式串patn在主串src中第一次出现的位置
- //plen为模式串的长度
- //返回patn在src中出现的位置,当src中并没有patn时,返回-1
- intcharconstintcharconstint
- int
- while
- if//如果相同,则两者++,继续比较
- else
- //否则,指针回溯,重新开始匹配
- //退回到最开始时比较的位置
- if
- return//如果字符串相同的长度大于模式串的长度,则匹配成功
- else
- return
回溯法做的多余的工作
在第二个位置发现还是不匹配,便再次回溯到第三个位置:
- 2、KMP算法的简介
KMP算法会直接把模式串移到匹配失效的位置上,如下图2-2,g处:
i-j..i-1i
0.. j-1
- //代码2-1
- intcharconstintcharconstintintconstint
- int
- int
- while
- if
- //匹配成功,就++,继续比较。
- else
- //当在j处,P[j]与S[i]匹配失败的时候直接用patn[nextval[j]]继续与S[i]比较,
- //所以,Kmp算法的关键之处就在于怎么求这个值拉,
- //即匹配失效后下一次匹配的位置。下面,具体阐述。
- if
- return
- else
- return
- 3、如何求next数组各值
j_next:
j_next-1
图3-1 求j-next(最大的值)的三个步骤
我们用变量k来代表求得的j_next的最大值,即k表示这S[i]、P[j]不匹配时P中下一个用来匹配的位置,使得P[0…k-1] = P[j-k…j-1],而我们要尽量找到这个k的最大值。如你所见,当匹配到S[i] != P[j]的时候,最大的k为1(当S[i]与P[j]不匹配时,用P[k]与S[i]匹配,即P[1]和S[i]匹配,因为P[0]=P[2],所以最大的k=1)。
图3-2 j_next=1,即最大的k的值为1
图3-3 第二步匹配中,跳过P[0](a),只需要比较 P[1]与S[3](b)了
接下来的问题是,怎么求最大的数k使得p[0…k-1] = p[j-k…j-1]呢。这就是KMP算法中最核心的问题,即怎么求next数组的各元素的值?只有真正弄懂了这个next数组的求法,你才能彻底明白KMP算法到底是怎么一回事。
P[0…k-1] = P[j-k…j-1]的k存在,而且还要最大的一个k。下面咱们换一个角度思考。
- P[j] = p[k], 那么next[j+1]=k+1,这个很容易理解。采用递推的方式求出next[j+1]=k+1(代码3-1的if部分)。
- P[j] != p[k],那么next[j+1]=next[k]+1(代码3-1的else部分)
,你将看到,由这个方法得出的next值还不是最优的,也就是说是不能允许P[j]=P[next[j]]出现的。ok,请跟着我们一步一步登上山顶,不要试图一步登天,那是不可能的。由以上,可得如下代码:
- //代码3-1,稍后,你将由下文看到,此求next数组元素值的方法有错误
- voidcharconstintint
- int
- int
- while
- if//循环的if部分
- else//循环的else部分
- //递推
图3-4 求next数组各值的示例
解KMP算法的关键就在于这个求next值的过程。Ok,如下,咱们再次引用一下上述求next数组各值的核心代码:
nextval[0] = -1,我们得到第一个next数组元素值即-1(注意,咱们现在的目标是要求nextval[i]各个元素的值,i是数组的下标,为0.1.2.3);
图3-5 第一个next数组元素值-1
nextval[1] = 0;
图3-6 第二个next数组元素值0
nextval[2] = 0;
图3-7 第三个next数组元素值0
nextval[3] = 1(由下文的第4小节,你将看到,求出的next数组之所以有误,问题就是出在这里。正确的解决办法是,如下文的第4小节所述,++i,++j之后,还得判断patn[i]与patn[j]是否相等,即杜绝出现P[j]=P[next[j]]这样的情况);
-1,0,0,1。如下图3-8所示:
图3-8 第四个next数组元素值1
图3-9 第一步,在p[3]处匹配失败
-1,0,0,1)。但在p[1]处还是匹配失败(即p[1]!=s[3])。图3-10 第二步,p[1]处还是匹配失败
图3-11 第三步,p[0]与s[3]还是不匹配
图3-12 第四步,跳出循环,输出结果i-plen=4
所以,综上,总结上述四步为
- P[3]!=S[3],匹配失败;
- nextval[3]=1,所以P[1]继续与S[3]匹配,匹配失败;
- nextval[1]=0,所以P[0]继续与S[3]匹配,再次匹配失败;
- nextval[0]=-1,满足循环if部分条件j==-1,所以,++i,++j,主串指针下移一个位置,从P[0]与S[4]处开始匹配,最后j==plen,跳出循环,输出结果i-plen=4,算法结束。
上面的匹配过程隐藏着一个不容忽略的问题,即有一个完全可以改进的地方。对的,问题就出现在上述过程的第二步。
-1,0,0,1是有问题的。有什么问题呢?就是不容许出现这种情况P[j]=P[next[j]]。为什么?
图3-13/14 求next数组各值的错误解法



- 4、求解next数组各值的方法修正
即不能有这种情况出现:P[3]=b,而竟也有P[next[3]]=P[1]=b。
- //引用之前上文第3小节中的有错误的求next的代码3-1。
- voidcharconstintint
- int
- int
- while
- if//循环的if部分
- //这里有问题
- else//循环的else部分
- //递推
我们知道求next值的时候还要考虑P[j]与P[k]是否相等。当有P[j]=P[k]的时候,只能向前递推出一个p[j]!=p[k'],其中k'=next[next[j]]。修正的求next数组的get_nextval函数代码如下:
- //代码4-1
- //修正后的求next数组各值的函数代码
- voidcharconstintint
- int
- int
- while
- if//循环的if部分
- //修正的地方就发生下面这4行
- if//++i,++j之后,再次判断ptrn[i]与ptrn[j]的关系
- //之前的错误解法就在于整个判断只有这一句。
- else