图解算法——KMP算法

KMP算法

 

解决的是包,含问题。

Str1中是否包含str2,如果包含,则返回子串开始位置。否则返回-1。

示例1:

Str1:abcd123def     

Str2:123d

 

暴力法:

从str1的第一个字二哥符开始依此匹配,当以第一个字符开头的子串匹配不上时,开始从第二个字符开始。缺点:每一次匹配都是互相独立的。

复杂度为O(N*M),且N>=M。因为N<M就肯定不包含M长度的子串。

 

 

KMP算法将每一次的匹配进行了交涉。

此时,加入了字符串前后缀的概念。但要保证前后缀不能等于该字符串的长度。

以下分别以abcabcd和aaaaab字符串为例。

假设有这样的函数可以实现str2中每个位置的前面子串的最长前后缀。那么,

KMP算法的步骤就是:由原来的str1的i+1和str2的0匹配,改变为由str1的j和str2的0匹配,即由str1的x和str2的z匹配。如下图示:

举例说明:

 

 

 

 过程图解:

 

 加速实质解析:  否定了从str1中的i+1位置到j-1位置能配出str2的可能性。

进一步解析:

假设从str1中的i+1位置到j-1位置中有一个k位置开始匹配能配出str2。

那么就会存在在str1的k位置开始有一个后缀串和str2的前缀串相等。但是这又是和最长前后串的概念相违背的。故不成立。

再举一个把str2两次后推的例子(一次匹配不成功):

 

 抽象出来就是:第一次匹配从str1的i开始和str2的0位置开始,匹配到最后到了甲指向的x位置和乙指向的Y位置,发现不匹配。则乙指向Y所对应的最长前后缀长度,即str2中位置指针乙回退到图示位置。下一步继续乙和甲(X)的下一个位置进行比较,这是为什么呢?因为两个画圈圈的部分是相等的,因为最长前后缀原理。

 

 然后就是求解如何求得str2每个位置上的元素所对应的前面子串的最长前后缀位置。

原理如图:在0位置上设定为-1,1位置上设定为0,2位置上当0和1位置上相同时设定为1,否则设定为0。

 

 然后利用数学归纳法:求i位置上的索引长度。假设i-1位置上索引长度是4,则看位置为4的下一位,也就是位置为5的位置上(前缀的下一个字符)和i-1位置上的字符是否相等,如果相等,则i位置上的索引长度就是i位置上索引长度+1,即4+1=5。

 

 再举一例:

 

 如果i-1位置上的索引长度4所对应的位置上的元素c和i-1位置上的元素t是否相等,如果相等,则i位置上的元素k所对应的索引长度就是i-1位置上的索引长度+1,即是5。如果不相等,则比较i-1位置上的索引长度4所对应的位置上的元素c的索引长度对应的元素a和i-1位置上的元素t是否相等,如果相等,则i位置上的元素k所对应的索引长度就是元素a所在位置上的索引长度+1,即是1+1=2,否则继续比较直至到该位置上对应的索引长度为0或者-1。类似于递归,也是数学归纳,不断往前看,往前寻找。

 

 示例:

左侧是第一次比较c与t,第二次比较a与t都不对,a处已经对应的索引长度为0了,不能再继续向上寻找了,故i位置的k所对应的索引长度为0。

右图是第一次比较c与a,第二次比较a与a,匹配对了,此时c处对应的索引长度为2(ab),故i位置的k所对应的索引长度为2+1=3。

 

 

需要注意的是,永远和i-1位置上的元素去比较。直到比较相等,不相等就往前跳继续比较,直至到0。

代码如下:

public class KMPtest {
    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;
        //str2的以i为结尾的最长回文子串
        int[] next = getNextArray(str2);
        
        while(i1<str1.length && i2<str2.length) {
            if(str1[i1] == str2[i2]) {
                i1++;
                i2++;
            }else if (next[i2] == -1) {
                //说明str2指针回到了头部
                i1++;
            }else {
                //说明str2指针还没到头部,存在回文数组
                i2 = next[i2];
            }
        }
        
        return i2==str2.length ? (i1-i2) : -1;
    }

    private 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 index = 2;
        int cn = 0;//即将向前跳到的下标
        while(index<next.length) {
            if (str2[index - 1] == str2[cn]) {
                next[index++] = ++cn;
            }else if (cn > 0) {//还能往前跳
                cn = next[cn];
            }else {
                next[index++] = 0;
            }
        }
        return next;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        System.out.println("sub String index:"+getIndexOf("ababcababak","ababa"));
    }

}

输出结果为:

sub String index:5

 

 

 

实战题目:

题目:

树的包含。

就是在左树T1是否包含右树T2。

思路:将树序列化成字符串(字符数组),要把NULL也加入,否则单纯先序中序是不行的。然后利用KMP算法。

 

 加入NULL的意义:

 

 

二题:

看一个串是否是范式得到的,如:abcabcabcabc,即是abc*n得到的。

该思路就是看每个字符的索引长度值是否是倍数关系。

 

 

 

Over......

 

posted @ 2020-12-06 20:59  额是无名小卒儿  阅读(539)  评论(0编辑  收藏  举报