最大子序列的求解-分治方法

問題描述

問題:給定整數序列,求解其中最大子序列(連續的序列)。 java

思路分析

利用「分治」和遞歸的思想求解,在《數據結構與算法分析(Java語言描述)》Page29,做者給出了具體的java代碼。
整體思路是,原序列的子序列存在於三處,左、右和跨中點。將序列從中點分割,分別用遞歸求出算法

  1. 左邊最大子序列
  2. 右邊最大子序列
  3. 跨中點的最大子序列

其中,步驟3分別求出包含左側和右側包含中點端點的最大子序列,求和便是結果。
這樣最後三者中的最大者即爲序列的最大子序列。數據結構

源程序

//添加了求三數最大值的函數
public class MaxSubsequence {

    public static int maxSubSum3(int[] a) {
        return maxSumRec(a,0,a.length-1);
    }
    
    private static int maxSumRec(int[] a,int left,int right) {
        if(left==right) {
            if(a[left]>0)
                return a[left];
            else
                return 0;
        }
        
        int center =(left+right)/2;
        int maxLeftSum=maxSumRec(a, left, center);     //遞歸1
        int maxRightSum=maxSumRec(a, center+1, right); //遞歸2
        
        int maxLeftBorderSum=0,leftBorderSum=0;
        for(int i=center;i>=left;i--) {
            leftBorderSum+=a[i];
            if(leftBorderSum>maxLeftBorderSum)
                maxLeftBorderSum=leftBorderSum;            
        }
        
        int maxRightBorderSum=0,rightBorderSum=0;
        for(int i=center+1;i<=right;i++) {
            rightBorderSum+=a[i];
            if(rightBorderSum>maxRightBorderSum)
                maxRightBorderSum=rightBorderSum;            
        }
        
        return max3(maxLeftSum,maxRightSum,maxLeftBorderSum+maxRightBorderSum);            
    }
    
    public static int max3(int a,int b,int c) {
        int temp=a>b?a:b;
        int max3=temp>c?temp:c;
        return max3;
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        int[] arr={4,-3,5,-2,-1,2,6,-2};
        int maxSubSum=maxSubSum3(arr);
        System.out.println("the maxSubSum of array arr is:"+maxSubSum);
    }

}

程序分析

程序中,maxSumRec爲遞歸函數,函數開始爲是否到達遞歸基準的if判斷語句,而後分別是兩個遞歸語句,執行步驟①②。對於遞歸而言,遞歸語句後邊的語句暫時不執行,從遞歸語句處直接返回遞歸函數開始進行遞歸,在該遞歸語句處向內遞歸,直到到達基準語句,執行return跳出遞歸。遞歸語句計算出maxLeftSummaxRightSum函數

函數中,遞歸語句後邊的部分計算步驟③,在for循環中,固定中點端點,分別向左右兩側循環求和,利用if語句來更新包含中心端點的maxLeftBorderSummaxRightBorderSum。兩個變量求和便是③的最大子序列。性能

最後,求出最大者便可。spa

啓發:使用遞歸的時候,須要在遞歸語句前邊,提早設置好到達基準的條件(if,return/break),以便適時完成遞歸,跳出遞歸函數。調試

程序性能分析

注意到,該段程序的兩條遞歸語句出如今同一個函數內,起初我對遞歸的具體執行順序理解錯誤,覺得步驟①的遞歸執行得出maxLeftSum時,以後的步驟③語句並不會執行。實際進行程序調試後,發現,該段程序雖然看起來相對簡潔,但在執行時,會執行沒必要要的語句,執行邏輯並不明瞭。code

下面進行簡單的做圖分析:
圖片描述blog

注意到,遞歸函數從外層,沿着計算maxLeft的路徑,通過三次遞歸調用maxSumRec函數,到達基準,在基準層分別計算遞歸函數內部的三部分maxLeftmaxRight、左側最大子序列與右側最大子序列的和maxLeftBorderSum+maxRightBorderSum,並利用max3求出最大者返回。而後再從基準層向上,計算上一層maxRightSum,而後依次規律,逐步向上計算,最後計算出整個遞歸函數的maxLeft、maxRight和左右和,最後再執行max3函數,返回三者的最大值,獲得最終的最大子序列和。遞歸

整個過程相似二叉樹的每個節點都長三個不一樣大小的桃子,從根節點遞歸到最下層的樹葉,比較三個桃子的大小,摘取一個,再摘取同輩的其餘節點的桃子。依次向上摘,整體呈現從總到分,再從分到總的一個過程。

時間複雜度計算

程序主要運行部分在maxSumRec函數,分爲if判斷基準語句、兩條遞歸語句、兩個for計算半側邊界最大子序列語句。若序列元素個數爲N,T(N)爲該算法的運行時間。

在maxSumRec函數中,無論N爲多少,if基準語句部分老是運行固定的常數時間。

  • 若N=1,爲基準狀況,if語句執行,return返回結果,後續部分再也不執行,所以,T(1)=1。
  • 若N≠1,則後續的程序必須執行。兩個for循環中,索引0~N-1的元素都被接觸一次,運行時間爲O(N)。if基準語句、與if等縮進的幾條賦值語句運行時間爲常量,與O(N)比可忽略。剩餘爲兩條遞歸語句,每條語句執行時間爲T(N/2),由於左/右最大子序列求解,都是處理N/2大小的子序列。總的運行時間T(N)=2T(N/2)+O(N)。

參考書上的方法,使用規律遞推的方式,計算T(N)。簡化O(N)爲N,並假設N爲2的冪。T(2)=2×2,T(4)=4×3,T(8)=8×4,T(16)=16×5,若N=2^k,則T(N)=N(logN+1)=O(N log N

總結

整體而言,對於計算序列的最大子序列而言,書中的本算法略顯麻煩,後續會進行其餘簡潔方法的補充,本文藉此算法來深刻理解遞歸的過程。

相關文章
相關標籤/搜索