Java笔记——KMP算法

KMP算法

KMP算法介绍

KMP算法是一种串的模式匹配算法,用来求子串在主串的位置。是数据结构中比较难的一种算法。KMP算法的核心在于点在于如何利用子串生成next数组,和如何利用next数组来寻找子串在主串的位置

主要逻辑

在模式串匹配的过程中,一般的思路是利用两个指针,分别指向主串和模式串(子串),然后从头比较,如果两个指针指向的字符是一样的,就将两个指针同时后移,接着比较后面的字符。当两个指针指向的字符不再一样的时候,就要将指向主串元素的指针回溯到一定位置,同时指向模式串元素的指针也回溯到一定位置,然后重新比较。

当指向模式串元素的指针遍历完模式串的同时,恰好遍历完主串或者没有遍历完主串,则说明已经找到模式串在主串中的位置
匹配成功

当指向模式串元素的指针没有遍历完模式串的时候已经把主串遍历完了,则说明模式串在主串中不存在匹配的位置

匹配失败

这种思路最主要的地方在于如何让模式串右移的过程做到高效,此时就有了两种思路,一种是简单的BF算法(暴力算法)一种就是KMP算法

BF算法主要是最小程度的将模式串右移,从而达到不遗漏任何一个可能的目的。在模式串匹配的工程中,当主串上的一个字符和模式串不匹配的时候,就将指向主串元素的指针回溯到主串起始比较的下一个元素,指向模式串元素的指针回溯到模式串最开始元素,然后继续开始比较。这种思路主要每次比较失败都将模式串右移一个字符的长度,时间复杂度是O(n*m)

KMP算法相比于BF算法主要的特点是利用next数组提高模式串比较的效率。在模式串比较的过程中,根据已经生成next数组将将模式串最大限度的右移。在将模式串右移的过程中,还做到了指向主串元素的指针不回溯,按照next数组将指向模式串数组的指针进行回溯。时间复杂度为O(n+m)
KMP

Next数组

当我们在进行字符串匹配的BF算法过程中,因为在一遍一遍的遍历模式串才会导致时间复杂度那么高。当我们如果可以遍历完一遍模式串以后将模式串的规律存储到一个数组中,然后根据这个数组中存储的数据进行回溯的话,就会大大降低时间复杂度,这就是为什么要生成next数组的原因。

一个人能能走的多远不在于他在顺境时能走的多快,而在于他在逆境时多久能找到曾经的自己。 ——KMP

Next数组是KMP算法的核心,Next数组决定了如何将模式串做到最大程度的右移,在next数组中存储的数据代表着如果当前字符模式串匹配失败,指向模式串元素的指针要回溯的位置。这个回溯的位置一般是看模式串中相同的字符串前缀和字符串后缀,当一个字符串前缀和后缀一致的时候,就会进行按照字符串前缀和字符串后缀的长度进行生成next数组的数值。

在一个字符串中,它有字符串前缀和字符串后缀,前缀是指最后一个字符以外,一个字符串的全部头部的组合,后缀是指第一个字符以外,一个字符串的全部尾部组合。就以字符串"ababc",它的字符串前缀有a,ab,aba,abab,它的字符串后缀有c,bc,abc,babc,把每一个字符串前缀都当成一个独立的字符串,寻找每个字符串的公共前后缀。‘a’,‘ab’没有公共前后缀,"aba"存在公共前后缀,是’a’,所以生成的模式匹配值是1,“abab"存在公共前后串,是"ab”,所以生成的模式匹配值是2。最后字符串"ababac"生成的next数组是00120,生成好Next数组就可以根据这个数组进行KMP模式串匹配

KMP搜索

KMP搜索核心点之一就是如何利用next数组,已知next数组是根据模式串的规律进行生成的,如何利用这个规律将模式串匹配做到更快更高效就是KMP算法的一个关键。

根据Next数组的介绍可以得知,next的数值代表着最长模式串前缀子串的公共前后缀的最长长度,同时也代表着要回溯的位置。当模式串和主串的元素不再一致的时候,就要根据next数据将指向模式串字符的指针进行回溯,回溯的位置就是next数组的值。当next数组此时的值为0的时候,就要根据情况进行一遍检查。

代码解释

生成next数组

    //获取到一个字符串的部分匹配值
    public static int[] KMP_next(String dest) {
        //创建一个数组next,保存部分匹配值。长度跟模式串长度一致
        int[] next = new int[dest.length()];
        next[0] = 0;//如果字符串是长度为1 部分匹配值就是0
        //当字符串的长度为1的时候,没有前缀也没有后缀,更不可能有公共前后缀,所以默认是0
        //i从第二位开始,j从第一位开始,遍历整个模式串
        for (int i = 1, j = 0; i < dest.length(); i++) {
            //当dest.charAt(j) != dest.charAt(i),我们需要从next[j-1]获取新的j
            //知道我们发现有dest.charAt(j) == dest.charAt(i)成立才停止
            while (j > 0 && dest.charAt(j) != dest.charAt(i)) {
                //这里设置j>0是为了防止陷入死循环中(i指向的元素不等于第一个元素,并且j)
                j = next[j - 1];//当est.charAt(j) != dest.charAt(i),进行一个回溯,一直到dest.charAt(j) == dest.charAt(i)或者j已经指向第一个元素的位置
            }
            //当dest.charAt(j) == dest.charAt(i)满足时,部分匹配值就是+1
            if (dest.charAt(j) == dest.charAt(i)) {
                j++;//这里进行j++,刚好让j进一位,但是还是在i后面
            }
            next[i] = j;//将j的位置放进next数组中
        }
        return next;
    }

模式串匹配

	//KMP搜索算法
    public static int KmpSearch(String str1, String str2) {
        //调用KMP_next方法生成一个next数组
        int[] next = KMP_next(str2);
        //遍历主串
        for (int i = 0, j = 0; i < str1.length(); i++) {
            //这里也是一个回溯,但是这里的回溯是根据next数组,将指向模式串元素的指针回溯到指定位置,从而实现模式串的右移。
            if (j > 0 && str1.charAt(i) != str2.charAt(j)){
                j = next[j - 1];
            }
            //如果模式串和主串的被指向的元素相同就将j后移,循环结束后i会自动后移一位,所以这里就不用写i++
            if (str1.charAt(i) == str2.charAt(j)) {
                j++;
            }
            //判断j是不是已经指向模式串最后一个字符,如果指向了模式串最后一个字符,就说明已经在主串中找到跟模式串相同的子串,此时直接根据i,j就可以确定子串在主串中位置
            if (j == str2.length()) {
                return i - j + 1;
            }
        }
        //当主串遍历完成但是没有提前return的时候,说明没有模式串匹配失败,没有在主串中找到和模式串相同的子串,此时就要返回一个主串中不存在的位置用来说明没有找到,一般用-1来说明
        return -1;
    }

for循环里面的三个if判断不能调换顺序,判断j是否指向模式串最后一个元素的位置的if判断必须放到后面,因为放到开头时会发生i已经自增一次的情况,return语句也要相应的发生变化。

源码展示

import java.util.Arrays;

public class KMP {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABA";
        int[] next = KMP_next(str2);
        System.out.println("next=" + Arrays.toString(next));
        int index=KmpSearch(str1,str2);
        System.out.println(index);
    }

    //KMP搜索算法
    public static int KmpSearch(String str1, String str2) {
        int[] next = KMP_next(str2);
        //遍历
        for (int i = 0, j = 0; i < str1.length(); i++) {
            if (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[] KMP_next(String dest) {
        //创建一个数组next,保存部分匹配值
        int[] next = new int[dest.length()];
        next[0] = 0;//如果字符串是长度为1 部分匹配值就是0
        for (int i = 1, j = 0; i < dest.length(); i++) {
            //当dest.charAt(j) != dest.charAt(i),我们需要从next[j-1]获取新的j
            //知道我们发现有dest.charAt(j) == dest.charAt(i)成立才停止
            while (j > 0 && dest.charAt(j) != dest.charAt(i)) {
                j = next[j - 1];
            }
            //当dest.charAt(j) == dest.charAt(i)满足时,部分匹配值就是+1
            if (dest.charAt(j) == dest.charAt(i)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

都不会的鲨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值