匹配字符串的KMP算法

如果要学KMP算法,就要先知道next数组,什么是next数组呢?举个例子:字符串“abcabc”,就是这个字符串的第i个字符前面的字符(不包括第i个本身)是否存在前缀和后缀相同的情况,如果存在,前缀或者后缀的长度就是next[i]的值,这句话后几个字不好理解。就是abcdab这个字符串,前缀是ab,后缀也是ab,这就叫做前缀和后缀相同。

求解“abcabc”的next数组

  1. 再次说明一下什么是next数组
    • next[0]也就是a之前的字符串是否存在前缀和后缀相同的情况,我们一眼就看出来,他前面就没有字符串,我们约定next[0]=-1;
    • next[1]也就是b之前的字符串是否存在前缀和后缀相同的情况,我们一眼就看出来,他前面只有一个a,我们约定next[1]=0;
    • next[2]也就是c之前的字符串是否存在前缀和后缀相同的情况,他前面是ab,所以next[2]=0;
    • next[3]也就是a之前的字符串是否存在前缀和后缀相同的情况,他前面是abc,所以next[3]=0;
    • next[4]也就是b之前的字符串是否存在前缀和后缀相同的情况,他前面是abca,这个字符串有一个前缀后缀一样,就是那个a,所以next[4]=1;
    • next[5]也就是c之前的字符串是否存在前缀和后缀相同的情况,他前面是abcab,这个字符串有一个前缀后缀一样,就是那个ab,所以next[4]=2;
  2. 这个数组上面咱们是用肉眼看到的,下面咱们用代码展现出来怎么求一个字符串的next数组:
    • 如果next[j]=k;表示是这个字符串从p0到pk-1等于pj-k到pj-1
    • 对于next[j+1]而言,需要满足p0到pk等于pj-k到pj,是否存在这个k值?
    • 因为p0到pk-1等于pj-k到pj-1,所以我们只需要比较pj到pk是否相等。
    private static int[] getNext(String pattern) {
        int[] next = new int[pattern.length()];
        next[0] = -1;
        int j = 0, k = -1;
        while (j < pattern.length() - 1) {
            if (k == -1 || pattern.charAt(j) == pattern.charAt(k)) {
                j++;
                k++;
                next[j] = k;
            }
            else {
                k=next[k];
            }
        }
        return next;
    }

如果好好看了上面分析的那几句话,这个算法还是很好理解的,k代表的就是上一步的next数组的值
这其实并不是真正的next数组,还有待改进,改进后的next数组讲完KMP之后再说。
这是第一次匹配:
这里写图片描述
如果用BF算法第二次从第二个开始就好了,KMP算法则不是,这会next数组的用途就发挥了,匹配串的角标五个开始失败的,next[5]等于2,所以接下来从匹配串的第一个字母对应着S2开始匹配,通过next数组知道前两个不用比较,直接比较T2和S5。也就是下面这幅图:
这里写图片描述
上面那段话你可能不理解,直接看代码就好了,KMP算法:

private static int indexOf(String target, String pattern) {
        int next[] = getNext(pattern);
        int targetLength = target.length();
        int patternLength = pattern.length();
        int i = 0, j = 0;// 分别为目标串和模式串的下标
        while (i < targetLength && j < patternLength) {
            if (target.charAt(i) == pattern.charAt(j)) {
                i++;
                j++;
            } else {
                j = next[j];
                // 判断剩下的够不够长
                if (targetLength - i + 1 < patternLength - j + 1) {
                    break;
                }
            }
        }
        if (j == patternLength) {//匹配成功
            return i - j;
        } else {
            return -1;
        }
    }

下面解决最后一个问题:优化后的next数组,
那字符串abcabc来举例
next[5]是等于2的,因为前面的字符串是abcab。假如说拿着他和abcabdabcabc来比较。第一次比较的是abcabd发现角标为五时对不住,按照上面的KMP算法,因为next[5]是2,所以下一次匹配是拿着abcabc中的第一个c和abcabdabcabc目标串的角标为5的字符d比较,明显因为第一次比较的时候就知道目标串的角标为5的字符不是c了。所以这是个例外,也就是next数组的应该有话的地方,就是next[j]和next[k]相等的时候应该特殊处理。

private static int[] getNext(String pattern) {
        int[] next = new int[pattern.length()];
        next[0] = -1;
        int j = 1, k = -1;//knext[0]的值,每循环一次就是上一步的next[k]的值
        while (j < pattern.length() - 1) {
            if (k == -1 || pattern.charAt(j) == pattern.charAt(k)) {
                j++;
                k++;
                if (pattern.charAt(j) == pattern.charAt(k)) {
                    next[j] = next[k];
                }else {
                    next[j] = k;
                }

            }
            else {
                k=next[k];
            }
        }
        return next;
    }

这里的话我上面解释的next数组的定义就不对了。因为next数组真正的是为KMP算法服务的,不是一个单独的事物。希望大家能懂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值