当前位置:   article > 正文

KMP算法---关于next数组最详细的解答_kmp算法next数组的作用

kmp算法next数组的作用

        一切都是最好的安排,但是我们为了实现最好的安排,每一步都要拼尽全力。

我们在一个母字符串中查找一个子字符串有很多方法。KMP是一种最常见的改进算法,它可以在匹配过程中失配的情况下,有效地多往后面跳几个字符,加快匹配速度。

kmp算法的精髓就在于next数组,从而达到跳跃式匹配的高效模式。




而next数组的值是代表着字符串的前缀与后缀相同的最大长度。

"前缀"指除了最后一个字符以外,一个字符串的全部头部组合;

"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

在KMP算法中有个数组,叫做前缀数组,也有的叫next数组,每一个子串有一个固定的next数组,它记录着字符串匹配过程中失配情况下可以向前多跳几个字符,当然它描述的也是子串的对称程度,程度越高,值越大,当然之前可能出现再匹配的机会就更大。

这个next数组的求法是KMP算法的关键,但不是很好理解,我在这里用通俗的话解释一下。

1.举一个Next数组的例子,注意next()方法里面求的就是这个数组。 

下面来说明这个数组的求法:

2.当前面字符的前一个字符的对称程度为0的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是0,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如agcta这个里面t的是0,那么后面的a的对称程度只需要看它是不是等于第一个字符a了。

3.按照这个推理,我们就可以总结一个规律,不仅前面是0呀,如果前面一个字符的next值是1,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是1,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是2了。有两个字符对称了。比如上面agctag,倒数第二个a的next是1,说明它和第一个a对称了,接着我们就把最后一个g与第二个g比较,又相等,自然对称成都就累加了,就是2了。

4.当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。

重点:

         1.next数组的每个值的含义:在某个数组下标处,以数组下标处的字母结尾的字符串,前缀与后缀的最大重合长度。比如next[13]=7,就是在agctagcagctagc这个串中最大重合长度为7。     

         2. 如果想要计算next[14]的值,此时就需要寻求next[13]的帮助(知道前一位的最大重合长度,在前缀最大重合长度的基础上再多取一个字母即多取一个下标为7的字母,在后缀最大重合长度的基础上再多取一个字母即多取下标为14的字母,比较这两个字母是否相等,如果相等,则在next[13]的基础上+1,如果不相等见4)。

        3.但是因为数组是从0开始的,取这个最大重合长度,自然就定位到最大重合的下一个字符,所以直接取next[13]=7的7这个值,去找s[7],此时实际是第8个字符。

        4.(难点**) 如果不相等时也不直接去第一个字符处比较(确实不相等s[14]!=s[7]),相当于重复了在2的操作,在s[7]处,将a换成t(将s[7]换成了s[14])继续找s[7]处的最大重合长度(此时需要寻找next[6]的帮助),不断循环,直到无法找到重合子串,即到第一位与首字母a进行比较。

  1. void makeNext(char s[],int next[])
  2. {
  3. int len = strlen(s);
  4. next[0]=0; //初始化
  5. for(int i=1,k=0;i<len;i++)
  6. {
  7. //这个while是最关键的部分,k就是前一位的最大重复个数,即next[i-1]
  8. while(k>0 && s[k]!=s[i]) //s[k]!=s[i]即完成s[i]和s[next[i-1]]的比较,
  9. //不相等,则需要进入循环不断往前追溯。即步骤4
  10. k=next[k-1];
  11. //等价于 k=next[next[i-1]-1]
  12. //等号右边的k起的只是下标的作用
  13. if(s[k]==s[i])
  14. k++; //相等就+1
  15. next[i]=k; //赋值
  16. }
  17. }

至此,KMP算法的Next数组求完。

KMP全部代码如下

  1. public class UseString {
  2. //有了Next数组,现在来写字符串匹配算法吧。
  3. public int KMP(String s1,String s2){
  4. //求的模式串的Next数组
  5. int i=0,j=0;
  6. int[] next=next(s2);
  7. for (int s:next
  8. ) {
  9. System.out.println(s);
  10. }
  11. //Next数组输出完毕
  12. for (;i<s1.length()&&j<s2.length();i++){
  13. //回退到相等的地方。这个j和前面next数组的K有什么关系?
  14. //在求next数组的时候,k是前一位匹配的最大重合长度,chars[k]!=chars[i]是比较在前一次的最长前缀的新增后一位,和加入新的元素的最长后缀的新添加一位是否一样,不一样则需要回退。这个k和i都是一个字符串的指针。
  15. //这里的j是正在和主串匹配的长度,能匹配到j,说明前j-1个元素和主串能进行匹配,当这一位不满足时,需要找前一位的最长重合长度。
  16. while (j>0&&s1.charAt(i)!=s2.charAt(j)){
  17. j=next[j-1];
  18. }
  19. if (s1.charAt(i)==s2.charAt(j)){
  20. j++;
  21. }
  22. if (j==s2.length()){
  23. return i-j+1;
  24. }
  25. }
  26. return -1;
  27. }
  28. public int[] next(String s){
  29. //获取字符数组
  30. char[] chars=new char[s.length()];
  31. for (int i=0;i<s.length();i++){
  32. chars[i]=s.charAt(i);
  33. }
  34. //得到next数组
  35. int[] next=new int[s.length()];
  36. next[0]=0;
  37. //k是前一位的最大重合个数
  38. for (int i=1,k=0;i<next.length;i++){
  39. //这块有些反逻辑,新手想不出来的。为什么这个while循环要在前面呢?
  40. //和判断条件有关系,
  41. //你是从第一个数往后想,第一个匹配然后第二个呢?
  42. //但是从整体上来讲,你应该是这样的:
  43. //找出这个数的前一个的最大重合子串,看最大重合子串,和这个数是否相等
  44. //如果不相等则回退,一直回退到相等或者没有相等的即此时k=0,跳出这个循环
  45. while (k>0&&chars[i]!=chars[k]){
  46. k=next[k-1];
  47. }
  48. if (chars[i]==chars[k]){
  49. k++;
  50. }
  51. next[i]=k;
  52. //判断循环结束的条件:为什么要这个循环呢?就是往next里面跳,不断的匹配最短的跳
  53. // 1.k值等于0(到了最前面)
  54. // 2.这个不匹配,要往前跳
  55. }
  56. return next;
  57. }
  58. public static void main(String[] args) {
  59. String test1="adaagctagcagctagctg";
  60. String test2="agctagcagctagctg";
  61. UseString useString=new UseString();
  62. System.out.println(useString.KMP(test1, test2));
  63. }
  64. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/847740
推荐阅读
相关标签
  

闽ICP备14008679号