KMP算法详解 (转载)

匿名 (未验证) 提交于 2019-12-03 00:18:01

最近在刷题的时候遇到了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-1
  2. //查找出模式串patn在主串src中第一次出现的位置
  3. //plen为模式串的长度
  4. //返回patn在src中出现的位置,当src中并没有patn时,返回-1
  5. intcharconstintcharconstint
  6. int
  7. while
  8. if//如果相同,则两者++,继续比较
  9. else
  10. //否则,指针回溯,重新开始匹配
  11. //退回到最开始时比较的位置
  12. if
  13. return//如果字符串相同的长度大于模式串的长度,则匹配成功
  14. else
  15. return


回溯法做的多余的工作

在第二个位置发现还是不匹配,便再次回溯到第三个位置:


  • 2、KMP算法的简介


KMP算法会直接把模式串移到匹配失效的位置上,如下图2-2,g处




i-j..i-1i
0.. j-1


  1. //代码2-1
  2. intcharconstintcharconstintintconstint
  3. int
  4. int
  5. while
  6. if
  7. //匹配成功,就++,继续比较。
  8. else
  9. //当在j处,P[j]与S[i]匹配失败的时候直接用patn[nextval[j]]继续与S[i]比较,
  10. //所以,Kmp算法的关键之处就在于怎么求这个值拉,
  11. //即匹配失效后下一次匹配的位置。下面,具体阐述。
  12. if
  13. return
  14. else
  15. 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。下面咱们换一个角度思考。

  1. P[j] = p[k], 那么next[j+1]=k+1,这个很容易理解。采用递推的方式求出next[j+1]=k+1(代码3-1的if部分)。
  2. P[j] != p[k],那么next[j+1]=next[k]+1(代码3-1的else部分)

,你将看到,由这个方法得出的next值还不是最优的,也就是说是不能允许P[j]=P[next[j]]出现的。ok,请跟着我们一步一步登上山顶,不要试图一步登天,那是不可能的。由以上,可得如下代码:

  1. //代码3-1,稍后,你将由下文看到,此求next数组元素值的方法有错误
  2. voidcharconstintint
  3. int
  4. int
  5. while
  6. if//循环的if部分
  7. else//循环的else部分
  8. //递推


图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

所以,综上,总结上述四步为

  1. P[3]!=S[3],匹配失败;
  2. nextval[3]=1,所以P[1]继续与S[3]匹配,匹配失败;
  3. nextval[1]=0,所以P[0]继续与S[3]匹配,再次匹配失败;
  4. 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数组各值的错误解法

]=P

=b,若再拿P[1]去与S

匹配则必败。
  • 4、求解next数组各值的方法修正

即不能有这种情况出现:P[3]=b,而竟也有P[next[3]]=P[1]=b

  1. //引用之前上文第3小节中的有错误的求next的代码3-1。
  2. voidcharconstintint
  3. int
  4. int
  5. while
  6. if//循环的if部分
  7. //这里有问题
  8. else//循环的else部分
  9. //递推

我们知道求next值的时候还要考虑P[j]与P[k]是否相等。当有P[j]=P[k]的时候,只能向前递推出一个p[j]!=p[k'],其中k'=next[next[j]]。修正的求next数组的get_nextval函数代码如下:

  1. //代码4-1
  2. //修正后的求next数组各值的函数代码
  3. voidcharconstintint
  4. int
  5. int
  6. while
  7. if//循环的if部分
  8. //修正的地方就发生下面这4行
  9. if//++i,++j之后,再次判断ptrn[i]与ptrn[j]的关系
  10. //之前的错误解法就在于整个判断只有这一句。
  11. else
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!