KMP算法next数组

暴力法求解模式匹配

example:
在这里插入图片描述

如果使用暴力法S匹配T,当主串指针指向第6个位、模式串指针指向第6个位时不匹配,此时主串指针会回溯到2,T指针回溯到初始位置1,重新开始比较,以此类推。

思想

在这里插入图片描述

KMP算法使得主串指针i不进行回溯,模式串指针根据前后缀重复的情况进行重新定位,从而提高效率
有模式串abcdex当匹配到x时匹配失败,已知a与bcde均不同,那么主串指针就不需要再返回bcde与模式串进行比较了,如此就减少了主串指针的回溯问题

最大重复前后缀

最大重复前后缀是串的前缀和后缀重复的字串
example

abcabc 最大重复前后缀为abc
abaca 最大重复前后缀为a
aaaa 最大重复前后缀为aaa(不是aaaa)

有重复时

上边记录的是不会出现重复的情况,当出现重复情况时是如何?
example
在这里插入图片描述

当匹配到位置x处失败
先考虑i回溯问题:已知a与b、c不相等,那么i指针必然不需要回溯到b、c处与T串作比较,接着考虑第四位a与第五位的b,发现与第一位的a、b一致,在主串比较到第四五位时已经比较过a、b了,那么a、b的比较也是不需要的,固i仍然不需要回溯,j指针从第三位开始与主串比较即可(最大重复前后缀处的下一位)
在这里插入图片描述

总结KMP算法的优势:

  1. 主串指针不需要进行回溯(只需要模式串移位即可)
  2. 不匹配时模式串也不一定从初始位置开始(找最大前缀后缀长度处开始就行,前缀和后缀一致)

next数组

根据分析,KMP算法与主串没有关系,取决于模式串的每个位置的前后缀长度,这个记录每个位置前后缀长度的数组就称为next数组。当匹配失败时使用next数组进行模式串的位置与主串当前位置的重新匹配,准确的说应该是最大前后缀长度+1,从下一位开始比较。

现在整个KMP算法的关键转化为next数组的求解

import java.util.Arrays;

// kmp模式匹配算法
public class KMP{
    private String pattern;
    private int[] next;
    private int pLen;
    public KMP(String pattern){
        this.pattern = pattern;
        this.pLen = pattern.length();
        this.next = new int[pattern.length()];

        this.genNext();
    }
    private void genNext(){
      int head = -1;// 前缀指针
      int tail = 0;// 后缀指针

      this.next[0] = -1;// 第一个失败模式串不移动,主串向后移动
      while (tail<this.pLen-1){
          if (head==-1||this.pattern.charAt(head)==this.pattern.charAt(tail)){
              head++;
              tail++;// 主串指针后移一位
              this.next[tail] = head;
          }else{
              head = this.next[head];//模式串指针回溯
          }
      }

      System.out.println(Arrays.toString(this.next));
    }

    public int indexKMP(String str){
        int i = 0;// 主串指针
        int j = 0;// 模式串指针
        while (i<str.length()&&j<this.pLen){
//            j=-1是匹配pattern的初始位置时就失败了
//            j指针从头开始,i指针+1
            if (j==-1||str.charAt(i)==this.pattern.charAt(j)){
                i++;
                j++;
            }else{
                j = this.next[j];// 不匹配时模式串指针回溯
            }
        }

        if (j>=pLen) return i-pLen;
        else return -1;
    }

    public static void main(String[] args){
        KMP kmp = new KMP("aaaab");

        System.out.println(kmp.indexKMP("aaaab"));
    }
}

上面实现中使用-1标记起始位置,此时主串指针+1.模式串指针仍然为起始位置。索引由0开始计值

比较难以理解的地方在于head=this.next[head]这一步

不相同时寻找head处的最大前后缀是多少,继续比较该处的值与当前tail指针处值即可,原理与KMP的原理相同,不过是自身(前缀)与自身(后缀)进行比较,当head和tail不同时tail指针不进行回溯,head进行回溯(弄懂了主串与模式串的KMP原理就弄懂了这里)。
引用’v_july_v’文章中的图
在这里插入图片描述
当P0到Pk-1与Pj-k到Pj-1相同,当Pj≠Pk时k回溯到next[k]继续与指针j处值比较,而指针j不进行回溯

参考:
超详细理解:kmp算法next数组求解过程和回溯的含义
从头到尾彻底理解KMP(2014年8月22日版)

KMP算法的改进

假设有模式串

在这里插入图片描述
按照我的实现思路,next数组为[-1,0,1,2,3],如果位置5处匹配失败,接着用位置4的a与当前主串值匹配,如果不相等接着使用位置3的a与主串值匹配,一直到位置1的a与主串值匹配,失败后next为-1,主串指针后移与位置1的a匹配。

可以发现位置1到位置4之间做了重复的操作,如果与位置4不同,那么必然也与位置1不同,直接可以将位置5的next指向位置1即可。

实现思路就是在next数组的基础上,如果前缀与后缀相同的时候,将前缀的next赋值给后缀当前位置this.next[tail]=this.next[head]

  private void genNext(){
      int head = -1;// 前缀指针
      int tail = 0;// 后缀指针

      this.next[0] = -1;// 第一个失败模式串不移动,主串向后移动
      while (tail<this.pLen-1){
          if (head==-1||this.pattern.charAt(head)==this.pattern.charAt(tail)){
              head++;
              tail++;// 主串指针后移一位
              if (this.pattern.charAt(head)==this.pattern.charAt(tail)){
                  this.next[tail] = this.next[head];// 如果前缀与后缀相同,则将前缀的next赋值给i位置
              }else this.next[tail] = head;
          }else{
              head = this.next[head];//模式串指针回溯
          }
      }

      System.out.println(Arrays.toString(this.next));
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值