KMP字符串匹配算法

介绍:

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n) 。

算法:

1 能解决什么问题

解决字符串匹配问题,有一个字符串A ,有个字符串B,问A是否包含B,如果包含返回A串与B串匹配开头的数组下标,如果不匹配则返回-1

A:abcabcdabce

B:abce

我们可以看到,A串的最后四个字符是和B串一样的,所以是可以匹配上的,但如果写代码逻辑,我们需要写两层循环,首先循环A的第一个和B的第一个进行比较,如果匹配上了,那A的第二个和B的第二个去继续匹配,直到B都匹配上,如果没匹配上,则是再从A的第二个字符和B的第一个字符再进行比较,直到循环匹配上或A的所有字符都比较完,停止循环。

这个基本的算法,时间复杂度是O(m*n),如果遇到很大的串,就会很慢,而KMP可以将时间复杂度降到O(m+n),这里m是指A串的长度,n是指B串的长度。

2算法引入

A:abcabcdabce

B:abce

还是这个串,我们用眼睛直接去做比对,来探寻一下,大脑是怎么匹配的。

        首先,我先看到了第一个字符都是a,然后我继续看到了开头都是abc,发现匹配上了3个,就差最后一个了,我再看A串第四个竟然是a不是e,这里难免有点小沮丧。

        但是我不会从A串的b开始匹配,因为我之间都比较过了嘛,abc中间的bc是不能再匹配上的,很明显,我从A串的四个会开始匹配,这个时候我有看到了一个abc 这个 abcabcdabce,然后发现结尾又是个d,哎,只能再匹配了,但我也不会从d匹配,因为B串开头是a嘛,然后从d后面开始匹配,最后匹配到了abce

        从这个过程可以看出,我通过之前的比较,脑子记了一些东西,得出,如果按顺序继续暴力的去匹配,肯定匹配不上的结论,从而减少了匹配的次数,更快的完成了匹配。

        KMP就是这样,他通过一些数据记录,从而避免了多余无效的比较,并且还能他给出当前最能匹配多长的串下标是哪个,从而继续匹配。

3 算法逻辑

算法主体分为2部分,1 是生成next数组 2是匹配 

1 next数组生成

next数组,这个是由于字符串B进行生成的,是匹配过程中的工具,我们先看下如何生成next数组

A:abcabcabce

B:abcabce

需要新建一个next数组 长度和B串的长度一样,然后算出数据如下

abcabce
-1000123

说下 这个数字是怎么算的

abcabce  e字符前面 abcabce  是一样的,代表前面有3个一样的 所以e下面是3

再多几个例子

  aab  分别是-1 0 1 因为a和a对称

aaab  分别是-1 0 1 2 因为a和a对称   aa 和 aa对称,

aaaba  分别是-1 0 1 2  0因为a和a对称   aa 和 aa对称,最后一个a前面就没对称的了

再说咱们的字符B是怎么算的

第一个字符 和第二个字符 默认 -1 0 

第三个字符c 看 第一个字符a和第二个字符b 判断不是对称

第四个字符a  首先第三个字符c下面的数据是0,第一个字符a和第二个字符c判断不对称,所以写0

第五个字符b 首先第四个字符a下面是0,第五个字符开始对比 第四个字符和第一个字符,一样,所以写1

第六个字符c 先看第五个字符b下面是1,所以下标1代表第二个字符,拿第二个字符和第五个字符比,一样,所以第五字符值+1 = 2 ,第六个字符写2 

第七个字符e先看第六个字符c下面是2,所以下标2代表第三个字符,拿第三个字符和第六个字符比,一样,所以第六字符值+1 = 3 ,第七个字符写3  

2 匹配 

A:abcabcabce

B:abcabce

abcabce
-1000123

循环匹配,条件

1 循环A B 串进行匹配,直到匹配到了,或者匹配到A最后一个字符结束

2 进行一次匹配,我们发现按照顺序匹配A串和B串能匹配到第六个字符,当第七个字符a和e匹配的时候是匹配不上的,这个时候就要用到next数组了,e下面是三,所以 B下标匹配调到下标3的位置也就是第四个字符a,和A串的第七个字符a匹配,匹配上了,然后继续

A:abcabcabce

B:abcabce

3 大家发现,利用next串因为标注了B串对称的下标,又因为A串匹配到的和B串当前位置的之前都一样,所以根据next数组,直接就能跳过之前的匹配,减少了很多无用的比较。

4 假设B的第四个字符a和A的第七个字符是匹配不上的,那就要再看next数组,第四个字符对应0,所以就跳到0,也就是第一个字符再和A的第七个字符比较。

代码:


public class KMP {

    public static int getIndexOf(String s1, String s2) {
        //判断如果不需要比较就跳出
        if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) {
            return -1;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        //x是str1 下标 y是str2下标
        int x = 0;
        int y = 0;
        // O(M) m <= n   获取next数组
        int[] next = getNextArray(str2);
        // O(N) 循环知道x 或者 y 超出 字符串最大长度 匹配结束,这里x条件跳出说明str1已经都比较过了,y跳出说明已经都匹配上了
        while (x < str1.length && y < str2.length) {
            if (str1[x] == str2[y]) {
                //如果匹配上了x 和 y都加1
                x++;
                y++;
            } else if (next[y] == -1) { // y == 0
                //没匹配上,但是y已经是0了, y0的位置next数组是一定等于-1的
                x++;
            } else {
                //如果没匹配上,获取next数组中的值,根据值的下标继续用当前x位置和获取到的Y位置进行比较
                y = next[y];
            }
        }
        return y == str2.length ? x - y : -1;
    }

    public static int[] getNextArray(char[] str2) {
        if (str2.length == 1) {
            return new int[] { -1 };
        }
        int[] next = new int[str2.length];
        next[0] = -1;
        next[1] = 0;
        int i = 2; // 目前在哪个位置上求next数组的值
        int cn = 0; // 这个记录者着之前能匹配到最大的前串尾巴,当前是哪个位置的值再和i-1位置的字符比较
        while (i < next.length) {
            if (str2[i - 1] == str2[cn]) {
                // 配成功的时候  把cn+1后放到i位置,然后i++
                next[i++] = ++cn;
            } else if (cn > 0) {
                //匹配没成功,继续匹配比他小的可匹配串
                cn = next[cn];
            } else {
                //否则cn=0说明没有课匹配的了 直接给i赋值0
                next[i++] = 0;
            }
        }
        return next;
    }

    // for test
    public static String getRandomString(int possibilities, int size) {
        char[] ans = new char[(int) (Math.random() * size) + 1];
        for (int i = 0; i < ans.length; i++) {
            ans[i] = (char) ((int) (Math.random() * possibilities) + 'a');
        }
        return String.valueOf(ans);
    }

    public static void main(String[] args) {
        int possibilities = 5;
        int strSize = 20;
        int matchSize = 5;
        int testTimes = 5000000;
        System.out.println("test begin");
        for (int i = 0; i < testTimes; i++) {
            String str = getRandomString(possibilities, strSize);
            String match = getRandomString(possibilities, matchSize);
            if (getIndexOf(str, match) != str.indexOf(match)) {
                System.out.println("Oops!");
            }
        }
        System.out.println("test finish");
    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值