KMP算法

kmp算法初探,左神进阶班第一课。
先理解一个字符串的最长匹配长度,前缀和最长后缀,如abcabcd,对于字符d,它的最长前缀的最长后缀的匹配长度是3,abc=abc。aaaaab,这个字符串的最长前缀和最长后缀匹配长度是4,aaaa和aaaa(第一个a开始和最后一个a往前,前缀不能包括最后一个字符,后缀不能包括第一个字符),人为规定0位置的最长匹配长度为-1,1位置人为规定为0。对于后面的每个位置,都用某种方式求出它的最长前缀和最长后缀。形成一个数组,每个位置就是相对应的这个字符串的最长前缀和最长后缀的长度,当然数组大小等于字符串长度。我们称为next数组。如[-1,0,1,1,3,5]。
string 1,string 2,假设有这两个字符串,我们要解决的是str1包含不包含str2,现在假设str1从i位置和str2从0位置开始一一配对,假设到str1的X位置和str2的Y位置不相等,常理来讲我们应该从str1的i+1位置重新开始与str2的0位置开始再进行对比,这是暴力的方法,时间复杂度很高。
KMP算法不是这样,我们先找到str2 Y位置字符的最长字符串匹配长度,这个信息我们之前已经求到了,就是那个next数组,是包含str2所有位置的最长字符匹配长度。这个Y位置的最长后缀所对应的str1的那段字符串,我们规定第一个位置为j位置,重点:现在是j位置和str2的0位置开始配,但是注意最长前缀和最长后缀相等,所以相应的取str2的Z位置与str1的x位置开始配(比较),这样就大大加速了,这就是kmp的加速方式。
加速的实质是我们没有否定从j位置可以配出str2,但是我们否定了i位置到j位置可以配出str2,为什么?假设有一个k位置可以配出str2,k位置对应str2的0位置,那么从k出发到x位置这部分字符串应该等于str2等量的前缀相同,同时这段字符串又与str2的Y位置的前缀一一对应,这样得出来的最长前缀就比之前求出来的最长前缀还要长,可是我们之前是求出了最长字符串匹配长度,这显然不符合已知的情况,所以i到x位置之间是配不出str2字符串的。就是这样就省去了中间这一部分字符串的比较,加快了速度。这是KMP的精髓所在。如此重复,直到一个字符的最长前缀是0的时候,str1的x位置的下一个位置再与str2的0位置开始重新配。
kmp算法如下:

public class Code_01_KMP {

    public static int getIndexOf(String s, String m) {
        if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
            return -1;
        }
        char[] str1 = s.toCharArray();
        char[] str2 = m.toCharArray();
        int i1 = 0;
        int i2 = 0;
        int[] next = getNextArray(str2);
        while (i1 < str1.length && i2 < str2.length) {
            if (str1[i1] == str2[i2]) {
                i1++;
                i2++;
            } else if (next[i2] == -1) {
                i1++;
            } else {
                i2 = next[i2];
            }
        }
        return i2 == str2.length ? i1 - i2 : -1;
    }

    public static int[] getNextArray(char[] ms) {
        if (ms.length == 1) {
            return new int[] { -1 };
        }
        int[] next = new int[ms.length];
        next[0] = -1;
        next[1] = 0;
        int i = 2;
        int cn = 0;
        while (i < next.length) {
            if (ms[i - 1] == ms[cn]) {
                next[i++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            } else {
                next[i++] = 0;
            }
        }
        return next;
    }

    public static void main(String[] args) {
        String str = "abcabcababaccc";
        String match = "ababa";
        System.out.println(getIndexOf(str, match));

    }

}

以上是代码。
假设str和要匹配的string(称之为matchString)匹配到str的字符A和matchString的B字符不相等(意味着前面都一一对应并匹配),只是从字符A和字符B开始就不匹配,而此时我们已经求到了matchString字符B的最长字符匹配长度以及那个字符串。
因为现在已经有了matchString字符串的nextArr数组,nextArr[B]的值表示matchString[0…B]这一段字符串与后缀的最长匹配。我们把这串后缀记为b,前缀记为a。
那么下一次匹配检查不再像普通解法那样退回到stri+1重新开始与match[0]的匹配过程,而是直接让strj与matchk开始进行匹配检查。
str要匹配的位置仍然是j,不进行退回,对于matchString来说,相当于向右滑动,让match[k]滑动到与str[j]同一位置,然后进行匹配。

你可能感兴趣的:(算法)