KMP算法,你想知道的都在這裏!(算法優化篇)

簡介

寫這篇文章的主要目的是爲了優化上一篇文章中的步驟一。即:優化Next數組的求解方法html

與個人上篇文章是有很強的延續性,若是這篇文章看不懂,能夠看看個人上一篇文章:KMP算法,你想知道的都在這裏(算法理解)java

爲何須要優化?

由上一篇可知:我將KMP算法劃分爲了兩個部分:算法

  1. 求Next數組
/**
     * 該函數是爲了,根據目標子串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;
    }
  1. 匹配字符串
/**
     * 該函數用於查看目標子串在主串中第一次出現的位置
     * @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最重要的就是初始值和狀態轉移方程!(DP能夠專門出一個專題來講)。那這題,咱們照例來尋找Next數組的DP策略。優化

不難發現,Next數組的求解是具備延續性的,Next[i]依賴於Next[i-1],由於前綴和後綴是相互連續的。指針

咱們已經知道:Next[0]和Next[1]都爲0.由於這兩個值沒有咱們定義的前綴和後綴。code

能夠看見,匹配的狀況也就兩種:前綴指針=後綴指針 與 前綴指針≠後綴指針。htm

1.前綴指針=後綴指針

這種狀況就很好理解了,說明當前位置i的後綴指針suffix與前綴指針prefix指針的值是匹配的,在i-1的最長先後綴的基礎上累加便可。i的最長先後綴爲:Next[i] = Next[i-1]+1。blog

2.前綴指針≠後綴指針

這種狀況就是當前位置不匹配,說明與後綴相同的前綴必定是小於當前prefix的。

那咱們應該回溯,如何回溯呢?想到咱們匹配字符串的過程2了嗎?

咱們能夠利用Next數組回溯,Next[prefix]表示0~prefix的串中 最長相等先後綴。因此,不匹配,就讓prefix回溯到Next[prefix]。看是否相等,如何仍然不相等就一直回溯。直到相等爲止,或者是到位置0爲止。

DP代碼

//優化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;
	    }
	}
相關文章
相關標籤/搜索