【图解法】字符串匹配之KMP算法(画图多少有点累)

在这里插入图片描述

一、暴力搜索法

假设目标匹配字符串为蓝色所示的字符串,源字符串为黄色所示的字符串。

暴力搜索的流程图:

在这里插入图片描述

示例代码:

public class ViolenceMatch {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        int index = violenceMatch(str1, str2);
        System.out.println("str2第一次出现的位置为:" + index);
    }

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

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

        int i = 0;
        int j = 0;
        while (i < s1Len && j<s2Len) {
            if (s1[i] == s2[j]) {
                i++;
                j++;
            } else {
                i = i - (j - 1);
                j = 0;
            }
        }
        if (j == s2Len) {
            return i - j;
        } else return -1;
    }
}

二、KMP算法

假设目标匹配字符串为蓝色所示的字符串,源字符串为黄色所示的字符串。

1、计算源字符串的前缀表

  • 首先得出该字符串所有的前缀

    在这里插入图片描述

  • 然后,根据得到的各个前缀,观察每个前缀子字符串,得出最长前后缀的公共字符数量。比如,abab的前缀ab,后缀ab是最长的,数量为2

    在这里插入图片描述

  • 最终得出前缀表,并对前缀表进行优化。索引为0的元素设置为-1,并将最后一个位置的元素舍去,其他位置的元素依次后移

    在这里插入图片描述

2、使用KMP思想进行匹配

  • 首先,和暴力搜索算法一样,对每一个字符进行匹配

    在这里插入图片描述

  • 结果,到了第四个字符开始,就出现了匹配不上的情形。按照暴力搜索的算法来看,就要往后整体移动一个单位,但是按照KMP算法来讲,以上图出现匹配不上的位置为例,当前位置对应的字符为b,此时前缀表中对应的元素为1,那么就要将pattern字符串中索引为1的位置,移动到未匹配到的字符的位置

    在这里插入图片描述

    在这里插入图片描述

    好处就是能保证前面的字符已经是完整匹配上的,不需要重复匹配了。

  • 继续进行匹配,如果不行就按照上面一个步骤的方法进行移动

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  • 再匹配,再移动进行判断

    在这里插入图片描述

    在这里插入图片描述

  • 目前出现了这种稍微特殊一点的情形,当前未匹配到的字符所对应的前缀表中的元素值为-1。遇到这种情况的话,将源字符串pattern直接右移一个位置即可,将-1索引的位置对准当前未匹配到的字符的位置。

    在这里插入图片描述

    在这里插入图片描述

  • 在此进行匹配,发现匹配完全正确

    在这里插入图片描述

  • 在第一次匹配完成之后,后面还会不会有第二次匹配的可能呢?所以需要继续匹配。

    在这里插入图片描述

    在这里插入图片描述

  • 移动之后在进行判断,不行,就没有必要再比较了,因为已经到了target字符串的末尾了。

本示例对于KMP算法相比于暴力搜索算法的改良并不是非常明显。

可以拿Target:aaaaaaaab,Pattern:aaaab进行举例尝试。

三、KMP算法相关思路及代码

在这里插入图片描述

  • 首先,计算pattern字符串的前缀表

    在这里插入图片描述

  • 根据计算并移位,得出最后的前缀表

    在这里插入图片描述

  • 观察第一个步骤得出的前缀表的元素值。比如,ABABCA这个前缀所对应的前缀表元素值为1,而它后面的那个前缀ABABCAB,所对应的前缀表元素值为2。其实,需要比较当前新增的这个字符,与ABABCA这个前缀所对应的前缀表元素值所对应的索引字符进行匹配,如果相同就将公共前后缀加1。如图所示:

    在这里插入图片描述

    在这里插入图片描述

  • 但是,如果遇到那种后面的比较的字母不相同的呢?如下图所示:
    在这里插入图片描述

  • 显然,上面的字符串对应的公共前后缀为ABA ,而下面的那个字符串对应的公共前后缀为A。这种情况下,上面的这个字符串所对应的len为3,意味着前3个字符和后三个字符是相同的。咱们不能一个个的正着匹配,但是咱们可以斜着匹配啊。如下图所示:

    在这里插入图片描述

  • 代码示例:

    public class KMPAlgorithm {
        public static void main(String[] args) {
    		String str1 = "ABABABABCABAAB";
            String str2 = "ABABCABAA";
            int index = kmp_search(str1, str2);
            System.out.println(index);
        }
    
        //获取kmp前缀表
        public static int[] prefix_table(String dest){
            int[] next = new int[dest.length()];
            next[0] = 0;
            int len = 0;
            int i = 1;
            while (i< dest.length()){
                if (dest.charAt(i)==dest.charAt(len)){
                    len++;
                    next[i] = len;
                    i++;
                }
                else {
                    if (len>0){
                        len = next[len-1];
                    }
                    else {
                        //此时len肯定为0了
                        next[i] = len;
                        i++;
                    }
                }
            }
            return next;
        }
        //将前缀表整体右移一位
        public static void move_prefix_table(int[] next){
            int length = next.length;
            for (int i = length-1; i >0 ; i--) {
                next[i] = next[i-1];
            }
            next[0] = -1;
        }
    
        //kmp算法
        public static int kmp_search(String source,String dest){
            int n = dest.length();//n表示要匹配字符串的长度,用j表示(短的)
            int m = source.length();//m表示被匹配字符串的长度,用i表示(长的)
            //得到前缀数组
            int[] prefix_table = prefix_table(dest);
            move_prefix_table(prefix_table);
            int i = 0,j = 0;
            while (i < m){
                //在此输出结果
                if(j==n-1&&source.charAt(i)== dest.charAt(j)){
                    return i-j;
                }
                if (source.charAt(i)==dest.charAt(j)){
                    i++;
                    j++;
                }
                else {
                    j = prefix_table[j];
                    if(j==-1){
                        i++;
                        j++;
                    }
                }
            }
            return -1;
        }
    }
    

    在这里插入图片描述

本人表达能力和业务水平有限,望读者海涵。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值