KMP算法

KMP算法

1.KMP算法的应用场景:字符串匹配问题。

假设str1 = BBC ABCBAB ABCDABCDABDE, str2 = ABCDABD,然后判断str1是否还有str2,如果存在,就返回第一次出现的位置 ,如果没有返回-1。

解法1:暴力匹配算法

假设str1匹配到i位置,字串str2匹配到j位置,则

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

2)如果匹配不成功(即str[i]!=str[j]),令i = i-j+1(i回到开始匹配的字符的后一个位置), j = 0(回到初始位置),相当于每次匹配失败,i回溯,j重置。

由此可以看出暴力匹配方法会有大量回溯,浪费大量时间,不可行。

2.KMP算法介绍

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

2)部分匹配表产生

字符串:“bread”; 前缀: b, br, bre, bred 后缀:read, ead, ad, d

部分匹配值就是前缀和后缀的最长的共有元素的长度,以“ABCDABD”为例:

“A”的前缀和后缀都为空集,所以共有元素的长度为0。

“AB”的前缀为[A],后缀为[B],共有长度为0。

“ABC”的前缀[A,AB], 后缀为[BC,C],共有长度为0。

"ABCD"的前缀[A,AB,ABC],后缀为[BCD,CD,D],共有长度为0。

“ABCDA”的前缀[A,AB,ABC,ADCD],后缀为[BCDA,CDA,DA,A],共有元素为“A”,所以共有长度为1;

“ABCDAB”的前缀[A,AB,ABC,ABCD,ABCDA],后缀[BCDAB,CDAB,DAB,AB,B],共有元素为“AB”,所以共有长度为2;

“ABCDABD”的前缀[A,AB,ABC,ABCD,ABCDA,ABCDAB],后缀[BCDABD,CDABD,DABD,ABD,BD,D],共有长度为0;

由上可得出部分匹配表为

搜索词 A B C D A B D
部分匹配值 0 0 0 0 1 1 0

解法2 :

package com.ll.kmp;

public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        int[] next = kmpNext(str2);
        System.out.print(kmpSearch(str1, str2, next));

    }

    /**
     * kmp匹配算法
     *
     * @param str1 源字符串
     * @param str2 目标字符串
     * @param next 部分匹配表
     * @return 返回对应位置,未找到返回-1
     */
    public static int kmpSearch(String str1, String str2, int[] next) {

        for (int i = 0, j = 0; i < str1.length(); i++) {
            // 需要处理str1.charAt(i) != str2.charAt(j),去调整j的大小
            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;
    }

    /**
     * 获取字符串的部分匹配表
     *
     * @param dest 目标字符串
     * @return
     */
    public static int[] kmpNext(String dest) {
        // 创建部分匹配表
        int[] next = new int[dest.length()];
        next[0] = 0;  // 如果字符串长度位1, 部分匹配值为0;
        for (int i = 1, j = 0; i < dest.length(); i++) {

            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j = next[j - 1];
            }

            // 如dest.charAt(i) == dest.charAt(j),则部分匹配值j++
            if (dest.charAt(i) == dest.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

posted @ 2021-05-19 11:11  撑起一片阳光  阅读(294)  评论(0编辑  收藏  举报