• 图解算法——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......

  • 相关阅读:
    googleMap JsAPI
    格式化Json代码
    baidu map JSAPI
    ajaxFileUpload与KindEditor
    Intent 意图
    初识Android
    Android环境搭建
    Android入门知识梳理
    网页版计算器
    使用socket实现简单的聊天功能
  • 原文地址:https://www.cnblogs.com/gjmhome/p/14094299.html
Copyright © 2020-2023  润新知