1. 程式人生 > >Maximum subarray problem(“最大和子陣列”問題)與Kadane’s algorithm

Maximum subarray problem(“最大和子陣列”問題)與Kadane’s algorithm

最大和子串:在電腦科學中,最大子陣列問題就是在給定一維陣列中尋找和最大的連續的子陣列,並確定起點索引i,終點索引j和最大和。(該定義翻譯自wikipedia)

                                                          

最直接的方法就是窮舉每一個子串計算和:暴力搜尋1(Brute force)

def findMaxSum(arr):
    n=len(arr)
    maxSum=arr[0]
    beginIndex=0
    endIndex=0
    for i in range(n):
        for j in range(i,n):
            tempSum=0
            for k in range(i,j+1):
                tempSum += arr[k]
            if maxSum<tempSum:
                maxSum=tempSum
                beginIndex=i
                endIndex=j
    return maxSum,beginIndex,endIndex

該演算法複雜度為O(n^3)很容易發現,在確定子陣列起點索引i後,j在增大的過程中求和運算存在大量冗餘計算,故改進為:採用一個臨時變數將從i到j的和儲存下來,每次j增大時,這個臨時變數只要加上一個陣列值就行了:

def findMaxSum1(arr):
    n=len(arr)
    maxSum=arr[0]
    beginIndex=0
    endIndex=0
    for i in range(n-1):
        tempSum=0  #這裡用臨時變數儲存i到j的和
        for j in range(i,n):
            tempSum += arr[j]
            if maxSum<tempSum:
                maxSum=tempSum
                beginIndex=i
                endIndex=j
    return maxSum,beginIndex,endIndex

該演算法時間複雜度為O(n^2),還能不能繼續優化?

 

分治(Divide & Conquer):遞迴地將原問題二分得到子問題,直到子問題為只有一個元素的子陣列為止。然後再把子問題歸併,歸併兩個子陣列時需要將子陣列A的“最大和子陣列”的和子陣列B的“最大和子陣列”的和以及“AB合併陣列”的“最大和子陣列”的和三者進行比較得到最大值。(回憶一下歸併排序中的分而治之的歸併思維

def findMaxSum2(arr,start,end):
    if start>=end:
        return arr[start],start,start
    mid=start+(end-start)//2
    maxSumLeft,beginLeftIndex,endLeftIndex=findMaxSum2(arr,start,mid)
    maxSumRight,beginRightIndex,endRightIndex=findMaxSum2(arr,mid+1,end)

    leftExtendMaxSum=leftExtendTempSum=0
    tempIndex1=0
    for i in range(mid,start-1,-1):
        leftExtendTempSum+=arr[i]
        if leftExtendTempSum>leftExtendMaxSum:
            leftExtendMaxSum=leftExtendTempSum
            tempIndex1=i

    rightExtendMaxSum=rightExtendTempSum=0
    tempIndex2=0
    for i in range(mid+1,end+1):
        rightExtendTempSum+=arr[i]
        if rightExtendTempSum>rightExtendMaxSum:
            rightExtendMaxSum=rightExtendTempSum
            tempIndex2=i

    maxSum=leftExtendMaxSum+rightExtendMaxSum
    beginIndex=tempIndex1
    endIndex=tempIndex2
    if maxSum<maxSumLeft:
        beginIndex=beginLeftIndex
        endIndex=endLeftIndex
        maxSum=maxSumLeft
    if maxSum<maxSumRight:
        beginIndex=beginRightIndex
        endIndex=endRightIndex
        maxSum=maxSumRight
    return maxSum,beginIndex,endIndex

該演算法的時間複雜度為O(nlogn)

下面來膜拜一下線性時間複雜的解決此問題的演算法!!!

Kadane’s algorithm演算法的intuition竟然是Dynamic Programme!!!

如果我們知道陣列中以索引位置對應元素結尾的“最大和子陣列”的和為,那麼以索引位置對應元素結尾的“最大和子陣列”的和為多少?或者說以結尾的“最大和子陣列”將結尾的“最大陣列和”作為字首。即:

                                                                     

因為該演算法使用了最優子結構(在以每個位置結尾的最大和子陣列是以一個更小且有所重疊的子問題來計算),可以看作是Dynamic Programme的一個應用,給大神跪了。

def findMaxSum3(arr):
    maxSum=-float("inf")
    beginIndex=0
    endIndex=0
    max_ending_here=maxSum
    currentStartIndex=currentEndIndex=0
    for currentEndIndex in range(0,len(arr)):
        max_ending_here+=arr[currentEndIndex]
        if max_ending_here>maxSum:
            maxSum,beginIndex,endIndex=max_ending_here,currentStartIndex,currentEndIndex
        if max_ending_here<0:
            max_ending_here=0
            currentStartIndex=currentEndIndex+1
    return maxSum,beginIndex,endIndex

只返回最大值的版本更簡潔,直接從維基百科上copy過來:

def max_subarray(A):
    max_ending_here = max_so_far = A[0]
    for x in A[1:]:
        max_ending_here = max(x, max_ending_here + x)
        max_so_far = max(max_so_far, max_ending_here)
    return max_so_far

下面測試程式碼:

import numpy as np
arr=np.random.randint(-100,100,1000)
print(findMaxSum(arr))
print(findMaxSum1(arr))
print(findMaxSum2(arr,0,len(arr)-1))
print(findMaxSum3(arr))

輸出一致,時間消耗差別明顯!!!

(1520, 219, 554)
(1520, 219, 554)
(1520, 219, 554)
(1520, 219, 554)