1. 程式人生 > >演算法導論—最大子陣列問題

演算法導論—最大子陣列問題

華電北風吹
天津大學認知計算與應用重點實驗室
日期:2015/6/30

問題描述:
比如你獲得了一個投資某個股票的機會,並且,你已經準確知道了將來幾天這一隻股票的相對於前一天的差值,比如為[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] (原始價格為[100,113,110,85,105,102,86,63,81,101,94,106,101,79,94,90,97])那麼就有一個問題,從那一天買入,哪一天賣出獲益最大?這裡就是一個最大子陣列問題。
最大子陣列問題:在一個數組中找出最大的非空連續子陣列

求解方法一:
暴力求解找出所有的組合,共有C(n,2)種選擇,時間複雜度Θ

(n2)

求解方法二:
分治法求解最大子陣列問題,計算複雜度Θ(nlogn)
分治法求解最大字陣列的思想是把每一個數組一分為二,每次考慮最大字陣列所在的三種可能情況:跨中點,中點左側,中點右側。演算法的計算量主要在於跨中點這種情況,中點單側主要是控制劃分深度,所以每一層計算複雜度是Θ(n),二分以後深度為logn,因此分治法的計算複雜度是Θ(nlogn)
程式碼如下

def MaxCrossSubArray(A,low,mid,high):
    LeftMaxSum=A[mid]
    leftSum=A[mid]
    leftIndex=mid
    for i in
range(mid-1,low-1,-1): leftSum=leftSum+A[i] if leftSum>LeftMaxSum: LeftMaxSum=leftSum leftIndex=i rightMaxSum=0 rightSum=0 rightIndex=mid for i in range(mid+1,high+1): rightSum+=A[i] if rightSum>rightMaxSum: rightMaxSum=rightSum rightIndex=i MaxSum=LeftMaxSum+rightMaxSum return
(MaxSum,leftIndex,rightIndex) def MaxSubArray(A,low,high): if low==high: return (A[low],low,high) mid=(low+high)//2 Left=MaxSubArray(A,low,mid) Cross=MaxCrossSubArray(A,low,mid,high) Right=MaxSubArray(A,mid+1,high) List=[Left,Cross,Right] result=sorted(List,key = lambda list : list[0],reverse=True) return result[0] a=[13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7] print(MaxSubArray(a,0,len(a)-1))

程式碼最後返回一個tuple,tuple包含三個元素,第一個值最大字陣列的值,後兩個分開始開始索引和終止索引。
這個程式碼寫完,除錯成功後,有些地方自己還不是特別明白。在這兒寫下來,從新整理一下思路,加深一下對遞迴的理解。
這個分治想法特別簡單,如果不考慮返回最大字陣列索引的話特別好理解,就是每一次迭代我都遞迴尋找單側最大的,然後與當前跨中點的比較,返回大的。
但是如果我想新加一個返回索引的功能時,可以看到,我的返回索引的地方只有兩個,是跨中點的那個函式和遞迴函式裡面遞迴到字陣列只有一個元素的時候。剛開始我的疑惑在於,如果只設置這一個返回索引的地方,是不是有問題呀?我的主要疑惑在於,如果出現連線怎麼辦,這裡是我多想了,因為在每一層的每一個分支,都要執行跨中點的程式碼,逐層向上的跨中點程式碼,就是為了包括下層的連線情況。或者說這裡的三種情況的劃分已經把所有的情況都包括了。

求解方法三:
動態規劃解法。
在這部分描述兩個等價的方法對應於兩種資料輸入格式。
如果輸入資料就是股票價格——思路是從前往後掃描陣列,記錄並更新掃描過程中的最小值,對掃描到的每個值與當前掃描到的最小值做差值,將差值最大值輸出。程式碼見如下int maxProfit(vector& prices)方法。
如果就是求解最大子陣列問題——也是對陣列進行掃描,不過相比於上面的儲存掃描過程中的最小值,這裡對掃描到的每個元素進行疊加,如果疊加結果為負,將和歸零(對應於上面演算法的更新最小值過程),從新疊加,儲存疊加到的最大值輸出。程式碼見int maxSubArray();
程式碼如下:

class Solution {
public:
    int maxProfit(vector<int>& prices)
    {
        int len = prices.size();
        if (len < 2)
            return 0;
        int cur = prices[0];
        int result = 0;
        for (int i = 1; i < len; i++)
        {
            cur = min(cur, prices[i]);
            result = max(result, prices[i] - cur);
        }
        return result;
    }

    int maxSubArray(const vector<int> arr)
    {
        int maxSum = 0;
        int currSum = 0;
        for (int i = 0; i < arr.size(); i++)
        {
            currSum += arr[i];
            if (currSum > maxSum)
                maxSum = currSum;
            if (currSum < 0)
                currSum = 0;
        }
        return maxSum;
    }
    int maxSubArray(const vector<int> arr, int &begin, int & end)
    {
        int maxSum = 0;
        int currSum = 0;
        int newbegin = 0;
        begin = 0;
        for (int i = 0; i < arr.size(); i++)
        {
            currSum += arr[i];
            if (currSum > maxSum)
            {
                maxSum = currSum;
                begin = newbegin;
                end = i;
            }
            if (currSum < 0)
            {
                currSum = 0;
                newbegin = i + 1;
            }
        }
        return maxSum;
    }
};

最大連續子陣列問題有個歧義的地方,就是如果全部是負數的話,子陣列取空還是取最大值的問題。這裡附上另一篇部落格。