KMP算法超详解

KMP算法是一种改进的字符串匹配算法,KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的

本文KMP算法可能与课堂中课本有所不同,但其中思想是一致的,均适用模式匹配,且理解难度低。那么我们就开始吧!


例子:

将BBC ABCDAB ABCDABCDABDE与ABCDABD匹配

要学会KMP算法,我们就要知道KMP算法的匹配过程是什么?我们先看一下KMP算法的匹配过程:

第一步:先从开头开始匹配

直到匹配到

下一步:直接跳跃匹配

又依次匹配

下一步

直接跳跃匹配,此时匹配成功。

这就是KMP算法的精妙之处,在依次匹配的基础下,有时可以直接跳跃匹配。其实学会KMP算法,也就是要学会什么条件下要跳跃匹配。

我们再回顾上述的匹配过程,其中要跳跃的步骤已经用带红框的图标出。我们先看第一次跳跃时,有什么特殊之处。

我们发现被红框框住的字符串是AB,疑?为什么是AB呢?是不是一脸懵?

我们不难发现模式串(ABCDABD)的首字符串也有AB,那么你想一想,我这次匹配虽然失败了,只有最后一个字符D没匹配成功,但前面都成功了。但我后面的AB匹配成功了。

而我们在进行下一次匹配时,一定是先找到与模式串开头的AB匹配时,再依次向后进行匹配。但是我上一次匹配发现,我后面的AB已经成功匹配了,而我要找的就是以AB开头的字符串来进行比较,

何曾不好好利用呢?我们这时就可以直接跳过。同理第二次跳跃也是如此

看到这里,你应该了解了KMP的匹配过程了。现在问题就是如何实现依次跳多少位了。下面直接列出next()数组,next()数组就是跳跃位数的判断依据

next数组由来:从模式串第二位B开始与模式串匹配,匹配不上,则是0连续匹配上则加一,中断则从0开始。

再举一个例子:

这时我们在转向这张图:

我们知道这个父串一次要跳4位,并且模式串已经匹配到达了D,下标j为6。所以根据这一规律。我们可得出每次跳跃的长度为j-next[j-1](即6-2=4)。但有时候next[j-1]可能为0,为了尽可能的提高效率,我们还要向前遍历,尽可能找到第一个next不为0的那位,找到时j也要发生相应变化。如

此时next[j-1]=0,,但我们前面的AB已经匹配成功了,此时不能浪费,我们要把下标j移到C,即4。然后在将父串移动j-next[j-1]位,即移动2位。

代码实现:

package Arithmetic;

public class KMP {
    public static void main(String[] args){
        String str1 = "BBC ABCDABABCDABCDABDE";
        String str2 = "ABCDABD";
        int next[]=kmpNext(str2);
        int index=kmpCompare(str1,str2,next);
        System.out.println(index);
    }
    //1.获取匹配值
    public static int[] kmpNext(String dest) {
        //创建一个next[]数组储存部分匹配值
        int next[] = new int[dest.length()];
        next[0] = 0;//第一位一定为0
        for (int i = 1, j = 0; i < dest.length(); i++) {
            //不相等就要从头开始比较
            if (dest.charAt(i) != dest.charAt(j)) {
                j = 0;
            }

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

    //    2.KMP比较
    public static int kmpCompare(String str1, String str2, int next[]) {
        int index = 0;//记录匹配成功时的起始位置
        for (int i = 0, j = 0; i < str1.length(); i++) {
            if (str1.charAt(i) == str2.charAt(j)) {
                index = i;
                //逐个比较
                while (i<str1.length() && j<str2.length()&&str1.charAt(i)==str2.charAt(j)) {
                    i++;
                    j++;
                }
                //如果j等于str2的长度,说明此时已经成功匹配
                if (j == str2.length()) {
                    return index;
                }
                //如果此时匹配不成功,我们要移动子串
                else {
                    //此时我们只要从next[j]往前找到第一个不为0的数,然后
//                把父串往后移(j-next[k]-1)位,
//                减一是因为for循环后会加一
                    int k = j;
                    while (k > 0 && next[k] == 0) {
                        k--;
                    }
                    //此时我们已经找到第一个不为0的匹配数
                    //把父串往后移(j-next[k]-1)位
                    i = index + j - next[k] - 1;
                    //此时要从头匹配
                    j = 0;
                }
            }
        }
        //匹配不成功,返回-1
        return -1;
    }

}

片尾一言:

若你决定灿烂,山无遮,海无拦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白沐沐vccc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值