寫這篇文章的主要目的是爲了優化上一篇文章中的步驟一。即:優化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; } }