KMP算法(java)

第3章 KMP算法

3.1 字符串匹配

在这里插入图片描述

3.2暴力匹配的算法

假设现在str1匹配到i位置,子串str2匹配到j位置,则

1)如果当前字符匹配成功(str1[i] == str2[j] ),则i++,j++,继续匹配下一个字符

2)如果匹配失败(str1[i] != str2[j] ) ,令i = i - ( j-1 ),j = 0;相当于每次匹配失败时,i回溯,j 被置为0

3)用暴力方法解决的话会有大量的回溯,每次只移动一位,若是不匹配,移动到下一位接着判断,浪费大量的实践

代码实现
在这里插入图片描述

package com.ldm.kmp;

/**
 * @author 梁东明
 * 2022/9/12
 * 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
 * 点击setting在Editor 的File and Code Templates 修改
 *
 * 字符串匹配
 */
public class ViolenceMath {
    public static void main(String[] args) {

        String str1 = " ldm mdl nomLn dnh";
        String str2 = "nh";
        int i = violenceMath(str1, str2);
        if ( i == -1){
            System.out.println("str1中没有str2");
        }else {
            System.out.println("str2在str1出现的索引位置在:" + i);
        }


    }
    //暴力匹配算法实现
    public static int violenceMath(String str1, String str2){
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();

        int s1Len = s1.length;
        int s2Len  = s2.length;

        int i = 0; //字符串s1的初始索引
        int j = 0; //字符串s2的初始索引

        //
        while ( i < s1Len && j < s2Len){
          if ( s1[i] == s2[j]){//当出现第一个字符相等,继续比较
              i++;
              j++;
          }else {
              //不相等的话,i 后退 j-1 步
              //j 回到 第一步  即置为0
              i = i - (j-1);
              j = 0;
          }
        }

        //当退出循环,表示已经把两组字符串遍历完成了
        //接下就要看一看是否出现字符匹配的现象了
        if ( j == s2Len){  //如果str1中有str2的话,那么str2 应该遍历到尾部
            return i - j;  //那么就把str2在str1出现的索引返回
        }else {
            return -1;
        }

    }

}

3.3 KMP算法基本介绍

1)KMP是一个解决模式串在文本串是否出现过,如果出现过,最早出现大的位置的经典算法

2)Knuth-Morris_Pratt字符串查找算法,简称:KMP算法,常用于在一个文本串内查找一个模式串P的出现位置,这个算法是由DonaldKnuth、vaughanPratt、JamesH.Morris三人于1977年联合发表,故取这三人的姓氏命名此算法

3)KMP方法算法就利用之前判断过信息,通过一个next数组,保存模串中前后最长公共子序列的长度,每次回溯时,就通过next数组找到,前面匹配过的位置,省去了大量的计算时间

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

ABCDA 因为头尾都是A,所以是1,ABCDAB,因为头尾都是AB,所以是2

移动位数 = 已匹配的字符数 - 对应的部分匹配值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ujpdCqRP-1662997893966)(C:\Users\86139\AppData\Roaming\Typora\typora-user-images\image-20220912174106308.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8scDpWvF-1662997893966)(C:\Users\86139\AppData\Roaming\Typora\typora-user-images\image-20220912174603089.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZL4dOBfz-1662997893967)(C:\Users\86139\AppData\Roaming\Typora\typora-user-images\image-20220912174846089.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KHJ9MSz2-1662997893968)(C:\Users\86139\AppData\Roaming\Typora\typora-user-images\image-20220912174920813.png)]

KMP

1、先得到子串的部分匹配表

2、使用部分匹配表完成KMP匹配

这里ABCDABD,当匹配上ABCDAB的时候,我们想让他跳过中间没用的部分,直接到第二个AB处

而对于ABCDAB这个字符串,因为前缀AB和后缀AB是一样的,我们就可以把第二个AB当作新的起点

而对于ABCDAB这个字符串,因为前缀AB和后缀AB是一样的,我们就可以把第二个AB当作新的起点

我们就可以把第二个(也就是那个后缀)当作新的起点,而这里当然是要用这个后缀的第一个字符

而想达到这个字符的位置,我们需要前进的步数恰巧就是(已匹配字符的总长度 - 后缀的长度)

也就是从前缀的第一个字母到后缀的第一个字母要前进几步

代码实现

获取一个字符串的(子串)的部分匹配值表

  //获取一个字符串的(子串)的部分匹配值表
    public static int[] kmpNext(String dest){
        //创建一个next数组保存部分匹配值
        int[] next = new int[dest.length()];

        for (int i = 1,j =0; i < dest.length(); i++) {
            //当dest.charAt(i) !=  dest.charAt(j)满足时,我们需要从next[j-1]获取j
            //直到我们发现dest.charAt(i) == dest.charAt(j)满足时,才退出
            //这时kmp算法的核心!!!!!!
            while( j > 0 && dest.charAt(i) !=  dest.charAt(j)){
                j = next[j-1];
            }

            //当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
            if ( dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }

在这里插入图片描述

有注解的完整代码

package com.ldm.kmp;

import java.util.Arrays;

/**
 * @author 梁东明
 * 2022/9/12
 * 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
 * 点击setting在Editor 的File and Code Templates 修改
 *
 * kmp算法
 */
public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        int[] next = kmpNext(str2);  //求str2的部分匹配值表
        System.out.println(Arrays.toString(next));
        int i = kmpSearch(str1, str2, next);
        if ( i == -1){
            System.out.println("str1字符串没有包含str2");
        }else {
            System.out.println("str2在str1出现的索引位置在:" + i);
        }
    }

    /**
     * 写出kmp搜索算法
     *
     * @param str1 str1
     * @param str2 str2
     * @param next str2的部分匹配值表
     * @return int  str2如果存在str1,就返回其在str1出现的索引,没有就返回-1
     */
    public static int kmpSearch(String str1, String str2, int[] next){

        for (int i = 1,j =0; i < str1.length(); i++) {
            //当dest.charAt(i) !=  dest.charAt(j) 时,我们需要从next[j-1]获取j
            //直到我们发现dest.charAt(i) == dest.charAt(j)满足时,才退出
            //这时kmp算法的核心!!!!!!
            while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
                j = next[j - 1];
            }

            //当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
            if (str1.charAt(i) == str2.charAt(j)) {
                j++;
            }
            if ( j == str2.length()){
                return i - j + 1;
            }
        }
        return -1;
    }

    //获取一个字符串的(子串)的部分匹配值表
    public static int[] kmpNext(String dest){
        //创建一个next数组保存部分匹配值
        int[] next = new int[dest.length()];

        for (int i = 1,j =0; i < dest.length(); i++) {
            //当dest.charAt(i) !=  dest.charAt(j)满足时,我们需要从next[j-1]获取j
            //直到我们发现dest.charAt(i) == dest.charAt(j)满足时,才退出
            //这时kmp算法的核心!!!!!!
            while( j > 0 && dest.charAt(i) !=  dest.charAt(j)){
                j = next[j-1];
            }

            //当dest.charAt(i) == dest.charAt(j)满足时,部分匹配值就+1
            if ( dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

无注解的完整代码

有注解和无注解的代码的区别是:

有注解的代码是我理解老师代码后额外添加自己的注解

无注解的代码是我凭借对kmp算法的了解,自己手敲出来的,

所以一个注解都没有,放心食用。

package com.ldm.kmp;

import java.util.Arrays;

/**
 * @author 梁东明
 * 2022/9/12
 * 人生建议:看不懂的方法或者类记得CTRL + 点击 看看源码或者注解
 * 点击setting在Editor 的File and Code Templates 修改
 */
public class MyKMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "caudicgbahvbajkccb";
        String str2 = "cb";
        int[] next = kmpNext(str2);
        System.out.println("部分匹配表 = " + Arrays.toString(next));

        int i = kmpAlgorithm(str1, str2, next);
        if ( i == -1){
            System.out.println("str1没有包含str2");
        }else {
            System.out.println("str2在str1出现的索引是 = " + i);
        }
    }

    public static int kmpAlgorithm(String str1,String str2, int[] next){
        for (int i = 0 ,j =0; i < str1.length(); i++) {
            while ( j > 0 && str1.charAt(i) != str2.charAt(j)){
                j = next[j-1];
            }
            if (str1.charAt(i) == str2.charAt(j)){
                j++;
            }
            if ( j == str2.length()){
                return i - j + 1;
            }
        }
        return -1;
    }

    public static int[] kmpNext(String dest){
        int[] next = new int[dest.length()];
        for (int i = 1, j= 0; i < dest.length(); i++) {
            while (j > 0 && dest.charAt(i) != dest.charAt(j)){
                j = next[j-1];
            }
            if (dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

本次KMP算法 的教程出自韩顺平的数据结构与算法

数据结构和算法教程,哔哩哔哩详细教程
在 160-163p.

最后,认识一下,我是小白。努力成为一名合格的程序员。期待与你的下次相遇。

KMP算法(Knuth-Morris-Pratt算法)是一种字符串匹配算法,用于在一个主串中查找一个模式串的出现位置。它的核心思想是利用已经匹配过的部分信息,尽量减少不必要的比较次数,从而提高匹配效率。 KMP算法的实现主要包括两个步骤:构建next数组和进行匹配。 1. 构建next数组: - 首先,我们需要定义一个next数组,用于存储模式串中每个位置的最长公共前后缀长度。 - 然后,从模式串的第二个字符开始,依次计算每个位置的最长公共前后缀长度。 - 最后,将计算得到的最长公共前后缀长度填入next数组中。 2. 进行匹配: - 在匹配过程中,我们需要维护两个指针:主串指针i和模式串指针j。 - 当主串和模式串的当前字符匹配时,i和j同时向后移动一位。 - 当主串和模式串的当前字符不匹配时,根据next数组的值调整模式串指针j的位置。 - 如果j等于模式串的长度,匹配成功,返回主串中匹配的起始位置。 下面是KMP算法Java实现示例: ```java public class KMP { public static int kmp(String text, String pattern) { int[] next = getNext(pattern); int i = 0, j = 0; while (i < text.length() && j < pattern.length()) { if (j == -1 || text.charAt(i) == pattern.charAt(j)) { i++; j++; } else { j = next[j]; } } if (j == pattern.length()) { return i - j; } else { return -1; } } private static int[] getNext(String pattern) { int[] next = new int[pattern.length()]; next[0] = -1; int i = 0, j = -1; while (i < pattern.length() - 1) { if (j == -1 || pattern.charAt(i) == pattern.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梁小樽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值