KMP算法详解与Java实现

KMP算法是数据结构串部分最重要的算法。
它由Knuth、Morris和Pratt共同提出,取首字母称为KMP算法。
它相较于Brute-Force(暴力)算法有了很大的进步,主要是充分利用已经比较过的部分匹配信息。

针对的问题—串的模式匹配

设有两个串 t 和 p ,想在 t 中找到一个与 p 相等的子串。
通常 t 称为目标串(target string),把 p 称为模式串(pattern string),因此这种查找也叫模式匹配。模式匹配成功则返回找到的 t 中子串首字符的索引值,不成功则 t 中不存在模式串 p,返回-1。

KMP算法的思路

暴力算法

也稍微提一下暴力算法吧,不然体现不出KMP的优势,毕竟KMP算法虽然代码优美简洁,但思想“深邃”(看不懂 )。
Brute-Force算法采用穷举思想,对目标串 t 的每一个字符都比较过去,与 p 有一个不符合就往后走,再一个一个比较,直到找到匹配串或到达 t 的最后也没找到就说明不存在。设两串的长度分别为 n 和 m ,该算法的最好时间复杂度为O(m),最坏时间复杂度为O(n x m),可以证明平均时间复杂度为O(n x m)接近最坏时间复杂度。

KMP算法如何优化

暴力算法的缺点在于没有充分利用已经匹配过的信息,而是每次推倒重来。
KMP算法最最根本的思路也还是“以空间换时间”,引入next数组保存部分匹配信息,因此next数组也可称为部分匹配表
在讲next数组的作用前,我们先来看一个概念“最长相等前后缀”。

最长相等前后缀:
字符串{abcsudabc}
前缀集合{a,ab,abc,abcs,abcsu,abcsud,abcsuda,abcsudab,abcsudabc}
后缀集合{c,bc,abc,dabc,udabc,sudabc,csudabc,bcsudabc,abcsudabc}
通过观察前后缀集合,在两集合中都有的最长子串(除了字符串本身)是abc,则abc称为最长相等前后缀

看懂最长相等前后缀就基本能理解next数组的作用了。next数组就是保存模式串 p 的各个子串的最长相等前后缀。例如模式串{abdabs},对应的next数组如下:

next数组下标012345
部分匹配值000120

保存该值的意义是将模式串 p 的前缀集合与目标串 t 的后缀集合对齐重合
在这里插入图片描述
在这里插入图片描述
这样就充分利用了之前的匹配信息。

Java实现

注释比较详细,大家可以自己慢慢领会。

public class KMPalgorithm {
    /**
     * @param target 目标串
     * @param pattern 模式串
     * @param next 用于存储部分匹配信息
     * @description 在目标串中找到模式串,若找不到返回-1
     * @return 返回-1表示没有找到,找到则返回第一个匹配位置
     */
    public static int kmpSearch(String target, String pattern, int[] next) {
        // 遍历target目标串
        for (int i = 0, j = 0; i < target.length(); i++) {
            // j负责模式串
            while (j > 0 && target.charAt(i) != pattern.charAt(j)) {
                j = next[j - 1];
            }
            // 匹配时j自增
            if (target.charAt(i) == pattern.charAt(j)) {
                j++;
            }
            // j等于pattern的长度时表示匹配完
            if (j == pattern.length()) {
                // i为目标串中匹配到的模式串的末尾,最后要加1因为数组从0开始
                return i - j + 1;
            }
        }
        return -1;
    }

    /**
     * @description 返回模式串的部分匹配信息,数组next[i]
     * 的值为模式串中从0-i位置的子串的”最大相等前后缀“
     */
    public static int[] kmpNext(String pattern) {
        // 创建一个next[]数组
        int[] next = new int[pattern.length()];
        // 字符长度为1时,部分匹配信息肯定为0
        next[0] = 0;
        for (int i = 1, j = 0; i < pattern.length(); i++) {
            // 一旦没有匹配上(将j移到next数组中最后一个为0的地方)
            while (j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
                j = next[j - 1];
            }
            // 前后缀匹配到了,j自增
            if (pattern.charAt(i) == pattern.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }

    public static void main(String[] args) {
        String target = "BBCABCDABABCDABCDABDE";
        String pattern = "ABCDABD";
        int[] next = kmpNext(pattern);
        System.out.println("next = " + Arrays.toString(next));
        int index = kmpSearch(target, pattern, next);
        System.out.println("index = " + index);
    }
}

最后

附上几个对我理解KMP算法起到很大帮助的博客与视频,感谢前人的智慧。
B站-尚硅谷-KMP算法
CSDN-漫画解释KMP算法
CSDN-KMP算法配图详解(对next数组的理解很有帮助)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值