写这篇文章的主要目的是为了优化上一篇文章中的步骤一。即:优化Next数组的求解方法html
与个人上篇文章是有很强的延续性,若是这篇文章看不懂,能够看看个人上一篇文章:KMP算法,你想知道的都在这里(算法理解)java
由上一篇可知:我将KMP算法划分为了两个部分:算法
/** * 该函数是为了,根据目标子串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; }
/** * 该函数用于查看目标子串在主串中第一次出现的位置 * @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; }
咱们不难分析:第一部分的时间复杂度为O(n^2)。第二部分的时间复杂度为O(m)。
这样,KMP算法的整体时间复杂度就是Max(O(n^2),O(m))。那这时候,若是目标子串的长度很长,就会大幅增大总体的计算时间。因此,虽然上述的Next数组求法易于理解,可是不实用。数组
可是咱们不难发现,无论i的数值是多少:subStr[0]和subStr[1]都有通过一次比较,由于subStr[0]一定是前缀的第一个值,而subStr[1]多是后缀的第一个值,因此能够减小比较次数吗?固然能够,咱们必须对其进行剪枝!剪枝策略就是DP函数
咱们知道,DP就是有记忆力的暴搜。根据以前的状态,求解后续的状态。DP最重要的就是初始值和状态转移方程!(DP能够专门出一个专题来讲)。那这题,咱们照例来寻找Next数组的DP策略。优化
不难发现,Next数组的求解是具备延续性的,Next[i]依赖于Next[i-1],由于前缀和后缀是相互连续的。指针
咱们已经知道:Next[0]和Next[1]都为0.由于这两个值没有咱们定义的前缀和后缀。code
能够看见,匹配的状况也就两种:前缀指针=后缀指针 与 前缀指针≠后缀指针。htm
这种状况就很好理解了,说明当前位置i的后缀指针suffix与前缀指针prefix指针的值是匹配的,在i-1的最长先后缀的基础上累加便可。i的最长先后缀为:Next[i] = Next[i-1]+1。blog
这种状况就是当前位置不匹配,说明与后缀相同的前缀必定是小于当前prefix的。
那咱们应该回溯,如何回溯呢?想到咱们匹配字符串的过程2了吗?
咱们能够利用Next数组回溯,Next[prefix]表示0~prefix的串中 最长相等先后缀。因此,不匹配,就让prefix回溯到Next[prefix]。看是否相等,如何仍然不相等就一直回溯。直到相等为止,或者是到位置0为止。
//优化Next数组求法,时间复杂度为O(n) private static int[] CalculateNext(String substr){ //初始化Next数组 int[] Next = new int[substr.length()]; int prefix = 0; //i就表明suffix+1 for(int i = 2; i < substr.length(); i++){ int suffix = i - 1; //状况1 while(prefix > 0 && substr.charAt(prefix) != substr.charAt(suffix)){ prefix = Next[prefix];//回溯prefix,直到相等,或为0 } if(substr.charAt(prefix) == substr.charAt(suffix)){//若是相等就让前缀自增,Next[i] = Next[i-1]+1 prefix++; } Next[i] = prefix; } return Next; }
这样,咱们就能让KMP算法的时间复杂度控制在O(n)便可
咱们经过将过程1和过程2进行封装,并对外提供一个接口,整个KMP算法就算完成了!
class KMP { //对外的KMP算法,返回Str中第一次出现subStr的位置 //若是没有出现subStr,则返回-1 public int kmp(String Str, String subStr) { //特殊状况:子串为空,返回位置0 if(subStr.equals("")) return 0; //过程1,求解Next数组 int[] Next = CalculateNext(substr); //过程2,匹配字符串 return findPosition(subStr,Str,Next); } //过程一 private int[] CalculateNext(String substr){ int[] Next = new int[substr.length()]; int prefix = 0; for(int i = 2; i < substr.length(); i++){ while(prefix > 0 && substr.charAt(prefix) != substr.charAt(i-1)){ prefix = Next[prefix]; } if(substr.charAt(prefix) == substr.charAt(i-1)){ prefix++; } Next[i] = prefix; } return Next; } //过程一废弃方案,由于时间复杂度太高 // private static int[] CalculateNext(String substr){ // int[] Next = new int[substr.length()]; // for(int i = 2; i < substr.length(); i++){ // int left = 0; int right = i - 1; // String l = Character.toString(substr.charAt(left)); // String r = Character.toString(substr.charAt(right)); // int maxLen = 0; // 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; // } // Next[i] = maxLen; // } // return Next; // } //过程二 private int findPosition(String subStr,String str,int[] Next){ int i = 0; int j = 0; while(j + subStr.length() - i <= str.length()){ if(subStr.charAt(i) == str.charAt(j)){ i++;j++; if(i == subStr.length()) return j - subStr.length(); } else { if(i == 0) j++; i = Next[i]; } } return -1; } }