KMP算法,你想知道的都在这里!(算法理解)

简洁

我相信不少人都据说过KMP算法(PS:在上数据结构的时候,这个算法自始至终都没想明白)java

你们也知道KMP算法是用来寻找目标子串的算法,可是都没有真正搞懂KMP。以前,我也是如此,我疑惑的有:程序员

  1. Next数组中的值是如何定下来的?
  2. 获得Next数组之后,又是如何遍历的?

但愿这篇文章能对大家有所帮助!算法

视频推荐

KMP这种算法,单单靠看文章,仍是很难弄懂的,结合图像,以及视频的帮助会更容易弄懂。这里我推荐一个视频:KMP算法数组

我的也是经过这个视频,恍然大悟的。这里仍是很是感谢这位做者的分享。数据结构

我的总结

经过上述视频,我想你们应该都有一个大概的了解了。该算法我将它划分为两个部分:ide

  1. 求Next数组
  2. 匹配字符串

我将分这两个部分进行细致讲解函数

第一部分

求Next数组是KMP算法的重点,也是难点!想搞懂这部分必定要看视频!必定要看视频!必定要看视频!我以为上述的视频,将这部分说的很清楚了。这里作一个总结:
(视频里将目标子串的第i号位置映射到Next指针的第i+1号位置。我这里根据程序员的习惯,将目标子串的第i号位置映射到Next指针的第i号位置)优化

我将Next[i]作一下定义:指针

Next[i]表示:code

  • 若是(substr.charAt(i) != str.charAt(j)),这时候,将i = Next[i],再进行字符串匹配。

这种表达虽然不严谨,可是我感受比较直观hhhh。也即:若是目标子串第i个的值与匹配串第j的值不一致的时候,将目标子串向右滑动,滑动到i = Next[i]。

Next[i]值的定义:

Next[i]的值为:

  • 第0位为头的前缀s1,与第i-1为尾的后缀s2,寻找他们的最长相同先后缀
  • 最长相同先后缀:就是s1与s2的值是相同的,可是s1和s2的长度都应小于i-1.

上面这两个定义值得细品,这也是KMP的精髓,也正是Next数组,KMP算法才能更加高效!

其中须要注意的点是:Next[0] = 0,Next[1] = 0,由于从第2个开始,s1和s2才有意义。

看完上述定义,我将求Next数组的函数实现出来。

注:该函数求Next数组时间复杂度较高,便于理解,可是实用性不高。优化策略做者采用DP,请看后一篇博客。

/**
     * 该函数是为了,根据目标子串subStr,求解该子串的Next数
     * @param 目标子串substr
     * @return subStr的Next数组
     */
static int[] CalculateNext(String substr){
    	//init
        int[] Next = new int[substr.length()];
    	//求解Next[i]
        for(int i = 2; i < substr.length(); i++){
            //第0位为头的l前缀,与第i-1为尾的r后缀
            int left = 0; int right = i - 1;
            String l = Character.toString(substr.charAt(left));
            String r = Character.toString(substr.charAt(right));
            int maxLen = 0;
            //当l与r均小于i-1的时候,扩大搜索最长相同先后缀
            while(left < i - 1){
                //若是两个字符串相同,说明这是 目前 最长的相同先后缀
                if(l.equals(r)) maxLen = l.length();
                left++;
                right--;
                //继续扩大搜索范围
                l = l + Character.toString(substr.charAt(left));
                r = Character.toString(substr.charAt(right)) + r;
            }
            //最终的maxLen即为Next[i]的值
            Next[i] = maxLen;
        }
        return Next;
    }

第二部分

这个部分就简单不少了,只要注意一点小细节,总体思路很是清晰直观:

在保证目标子串与主串匹配的过程当中,不会越界的状况下:进行目标子串的滑动操做

subStr.charAt(i) == str.charAt(j)的时候,i与j同时往右移动便可,当彻底匹配的时候,就返回

subStr.charAt(i) != str.charAt(j)的时候,i = Next[i]便可。

这里j须要注意一个细节,若是i == 0,须要j往右移一位,由于和主串的第一位都不匹配,前面就再也没有能匹配的串了。

/**
     * 该函数用于查看目标子串在主串中第一次出现的位置
     * @param 目标子串subStr
     * @param 主串str
     * @param Next数组
     * @return str中,第一次出现subStr的位置
     */
static int findPosition(String subStr,String str,int[] Next){
        //初始化两个字符串的指针
    	int i = 0;//i为目标子串的指针
        int j = 0;//j为主串的指针
    	//保证目标子串与主串匹配的过程当中,不会越界的状况下:
        while(j + subStr.length() - i <= str.length()){
            //当两者匹配的时候,i与j同时往右移
            if(subStr.charAt(i) == str.charAt(j)){
                i++;j++;
                //当彻底匹配的时候,返回
                if(i == subStr.length()) return j - subStr.length();
            }
            //不匹配的时候,主串指针j只有在i = 0的时候,才右移动。
            else {
                if(i == 0) j++;
                //i值都须要更新
                i = Next[i];
            }
        }
        return -1;
    }

总结

这里主要探讨的是Next数组,这个是KMP算法的核心!之前考试都是考Next数组的求解,可见Next数组的重要性。