KMP算法口语化简解

字符串匹配问题介绍

给定两个字符串str1:ABCD ABD ACDABCDABCDABDED,str2:ABCDABD。问str1是否包含str2。

字符创匹配问题暴力求解

暴力求解的方法

我们很容易就想到暴力求解该问题的方法,从str1的第一个字符开始与str2开始匹配,匹配成功则开始匹配后一个字符,不成功则拿str1的后一个字符重新开始匹配str2的第一个字符。代码写起来也非常简单,时间复杂度为o(mn)

public static boolean isMatch(String str,String pattern){
        int strLength = str.length();
        int patternLength = pattern.length();
        if(strLength<patternLength){
            return false;
        }
        for (int i = 0; i < strLength; i++) {
            int tempi = i;
            int j = 0;
            while(tempi<strLength&&str.charAt(tempi)==pattern.charAt(j)){
                tempi++;
                j++;
                if(j==patternLength){
                    return true;
                }
            }
        }
        return false;
    }

暴力求解存在的问题

举一个简单的例子str1:ABCDABCDABCD,str2:ABCDABD。(说明:i表示str1的index从1开始,j表示str2)用暴力求解来匹配时,str1与str2的前缀ABCDAB匹配成功后str1的下一个字母是C而str2下一个字母是D出现失配,

str1ABCDABCDABCD
str2ABCDABD

然后从i=2即(B),j=1与str2重新匹配。

str1ABCDABCDABCD
str2ABCDABD

必然失配,且后续一系列匹配全都失败。显然这样浪费了很多时间,我们可以看出,失配时j=7(D)与i=7(C)拥有相同前缀AB,这时候将j设置为3而i不变,这是AB与j=5,j=6(AB)匹配,继续向下匹配即可。很明显这样只需要在失配时重置str2继续匹配即可,而不用重置i而浪费大量时间做必然失败的匹配。相当于是利用了前面匹配的结果而少走弯路。具体原因会在后文介绍。

str1ABCDABCDABCD
str2ABCDABD

KMP算法求解

求解步骤

首先kmp算法会根据待匹配的字符串str2来生成一个数组,暂且叫它next数组(在后面进行详细讲解),该数组记录了当前失配后str2应该移动到什么位置继续匹配,就前一个例子str1:ABCDABCDABCD,str2:ABCDABD。此时的next数组为[-1,0,0,0,0,1,2]所以当下面失配情况发生时,

str1ABCDABCDABCD
str2ABCDABD

j应该直接等于next[j]即2,而i不变继续进行匹配

str1ABCDABCDABCD
str2ABCDABD

然后继续向后匹配,直到失败或者成功。

next数组详解

next数组内存储的是以该位置结尾的匹配串子串的部分匹配值(即对于str:ABCDABD,nex[3]指ABCD的部分匹配值),我理解的部分匹配值其实就是部分字符串的首尾是相同的,自己可与自己部分匹配。例如ABCDAB他们的开头和结尾都是AB则部分匹配值为2。官方定义是最长前缀与后缀匹配值,例如对于AACDAA,其前缀有A,AA,AAC,AACD,AACDA后缀有A,AA,DAA,CDAA,BCDAA有相同元素AA与A。注意这里取最长匹配,即AA长度为2。
接下来详细讲一下对于str2:ABCDABD next数组的详细求解过程。

next数组数值求法
next[0]子串A的部分匹配(0)
next[1]子串AB的部分匹配(0)
next[2]子串ABC的部分匹配(0)
next[3]子串ABCD的部分匹配(0)
next[4]子串ABCDA的部分匹配(1)
next[5]子串ABCDAB的部分匹配(2)
next[6]子串ABCDABD的部分匹配(0)

得到[0,0,0,0,1,2,0]
而next数组就是讲全部数字右移一位,空位补上-1。得到
next[-1,0,0,0,0,1,2]。
设str1:ABCDABCDABDE,str2:ABCDABD然后开始进行匹配

str1ABCDABCDABDE
str2ABCDABD

匹配到上图所示情况后(i=6,j=6)查next[6]得到2则直接将j设为2,按照下图开始继续比较

str1ABCDABCDABDE
str2ABCDABD

完整代码

建议将代码调试一下,特别是生成next数组那部分。

package others;

/**
 * KMP算法匹配字符串
 *
 * @Author dewey
 * @Date 2018/10/16 20:18
 */
public class KMP {
    public static void main(String[] args) {
        String str = "BBC ABCDAB ABCDABCDABDE";
        String pattern = "ABCDABD";
        int[] result = getNext(pattern);
        for (int i = 0; i < result.length; i++) {
            System.out.println(result[i]);
        }
        System.out.println(isMatch(str,pattern));
    }

    /**
     * 判断字符串是否匹配
     *
     * @param str
     * @param pattern
     * @return
     */
    public static boolean isMatch(String str,String pattern){
        int[] next = getNext(pattern);
        int i = 0;
        int j = 0;
        while(i<str.length()&&j<pattern.length()){
            if(j==-1||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                j = next[j];
            }
        }
        if(j==pattern.length()){
            return true;
        }
        return false;
    }

    /**
     * 获取模式串的next数组
     *
     * @param pattern
     * @return
     */
    public static int[] getNext(String pattern){
        int length = pattern.length();
        int[] next = new int[length];
        next[0] = -1;
        //表示部分匹配的长度
        int i = -1;
        //字符串的位置从0-(length-1)
        int j = 0;
        while(j<length-1){
            //当i==-1时或i,j位置字符相等时
            if(i==-1||pattern.charAt(i)==pattern.charAt(j)){
                ++i;
                ++j;
                //i==-1进来的,则next[j]==i(0),即表示在j位置失配后需要从头开始匹配
                //i!=1进来的,则next[j]==i(即已部分匹配的长度,0-i的子串与j-(j-i)的子串相等)
                // next[j] = i;匹配到j失败后可以直接跳到i继续匹配。
                next[j] = i;
            }else{
                //如果i!=-1且i,j位置不相等时,需要循环最后使得i==-1从而令j++重新开始计算最长前后缀匹配
                i = next[i];
            }
        }
        return next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值