KMP算法

场景

求一个字符串(模式串)在另一个字符串(主串)中的位置,称为字符串模式匹配。

KMP算法实现

该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

假设主串为txt,模式串为p,遍历比较时,主串下标为index1,模式串下标为index2。

主串为:ABCDABCDABE,模式串为:ABCDABE。

KMP流程说明:

  1. 比如当与主串比较时,发现到模式串的第7个字符E与主串的第7个字符D不匹配,此时index1 = 6,index2 = 6。
  2. 如果是暴力法,就需要index1 = 2,index2 = 0,重新开始匹配。
  3. 但是这样很没有效率,我们知道模式串中是有重复字符的:“AB”。
  4. 我们可以用KMP算法,主串的下标index1 = 6不变,模式串可以从第3个字符C继续开始比较,即index2 = 2。

代码实现的思路:

  1. 求出模式串p的next数组,next数组中存着每个下标回溯的位置。对于上面的例子来说,就是next[6] = 2.
  2. 当模式串p中的字符与主串中的字符不匹配时,主串txt的下标index1不变,模式串p的下标index2变为next[index2].

最难的部分就是求这个next数组。

我们的思路是这样:通过一个值,k,来记录字符前面有多少个字符是重复的。

代码如下:

import java.util.Scanner;

public class KMP {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String txt = in.nextLine();
        String p = in.nextLine();
        System.out.println(new KMP().search(txt, p));
    }

    public int search(String txt, String p){
        int[] next = new int[p.length()];
        this.setNext(p, next);
        //index1表示txt的下标,index2表示p的下标
        int index1 = 0, index2 = 0;
        while(index1 < txt.length() && index2 < p.length()){
            //如果index2等于-1,说明上次循环发现是第一个就不匹配,所以++
            //如果相等,就继续++
            //如果不等,index2回溯,index1不用动
            if(index2 == -1){
                index1++;
                index2++;
            }else if(txt.charAt(index1) == p.charAt(index2)){
                index1++;
                index2++;
            }else{
                //System.out.println("主循环之前" + index2);
                index2 = next[index2];
                //System.out.println("主循环之后" + index2);
            }
        }
        if(index2 >= p.length()){
            //System.out.println(index1 + " " + index2);
            return index1 - index2;
        }else{
            return -1;
        }
    }

    /**
     * 获取模板串的next数组
     * 条件:对于字符串p(“ABCDABD”),当前字符为w(最后一个字符'D')
     * 思路:w前面有两个字符(“AB”)与p最开头的字符相同,那么如果w不匹配,p的下标应该变为2,从p的第三个字符('C')开始比较
     * @param p
     * @param next
     */
    public void setNext(String p, int[] next){
        //next数组的0/1是固定的
        next[0] = -1;
        next[1] = 0;

        //k记录当前字符前面相同字符的个数,-1为初始值
        //比如字符串为ABCDABD,对于第6个字符B来说,k为1,对于最后一个字符D来说,k为2,前面AB相同
        int k = -1;

        //循环赋值,
        for(int i = 2; i < p.length(); i++){
            //对于k = -1的情况,说明前一个字符是不相等,应该从开头开始,k=0;
            //对于前一个字符与第k个字符相等的情况,应该对k进行++
            //对于k 不等于 -1,并且字符不相等的情况,k就需要变为0,重新开始,但是呢,不用重复从0开始,直接回溯到k就行了
            if(k == -1){
                k++;
                next[i] = 0;
            }else if(p.charAt(i - 1) == p.charAt(k)){
                k++;
                next[i] = k;
                System.out.println("这是" + i + "的" + k);
            }else{
                k = next[k];
                i--;
            }
        }
    }
}

参考:
https://www.cnblogs.com/imzhr/p/9613963.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值