Python 分治法-最大子數組問題(我本身有面試遇到過)

學習算法導論的時候,有一個買股票的例子,我以爲挺有意思的,書上是這麼說的: 假定你得到了投資揮發性化學公司的機會。與其生產的化學制品同樣,這家公司的股票價格也是不穩定的。你被准許能夠在某個時刻買進一股該公司的股票,並在以後某個日期將其賣出,買進賣出都是在當天交易結束後進行。爲了補償這一限制,你能夠了解股票未來的價格。你的目標是最大化收益。圖4-1給出了17天內的股票價格。第0天的股票價格是每股100美圓,你能夠在此以後任什麼時候間買進股票。你固然但願「低價買進.高價賣出」—在最低價格時買進股票,以後在最高價格時賣出,這樣能夠最大化收益。但遺憾的是,在一段給定時期內,可能沒法作到在最低價格時買進股票,而後在最高價格時賣出.例如,在圖4-1中,最低價格發生在第7天,而最高價格發生在第1天—最高價在前,鼓低價在後。其實就是求最大連續子數組的問題,求在連續的買入股票,使得收益最大,固然是用暴力求解的方法也能夠求出來,時間複製度爲O(n^2)。不過有更好的方法,那就是分治法,時間複雜度爲O(nlgn)算法

暴力解決:

#暴力求解最大子數組
def find_max_list(listn,n):                #listn:要求解的數組 n:數組大小
    sum=0
    list_result=[listn[0],0,0]   #結果爲列表,存儲和的最大值和結果子數組的位置 
    for i in range(n-1):   #這裏只循環到第listn[n-2],考慮到當i=n-1時j的取值
        sum=listn[i]           #i變更時,sum進入新的循環,從新賦值爲listn[i]
        if sum>list_result[0]:
            list_result=[sum,i,i]
        for j in range(i+1,n,1):
            sum=sum+listn[j]
            if sum>list_result[0]:
                list_result=[sum,i,j]
    if list_result[0]<listn[-1]:                #單獨與最後一個數字進行比較
        list_result=[listn[-1],n-1,n-1]
    return sum(list_result)
複製代碼
分治法分三步:
複製代碼
  1. 分:能夠將數組分爲兩部分,要儘可能均勻,因此最好是從中間分,結果能夠能在前半數組,可能在後半數組,也可能跨先後數組編程

  2. 治:咱們能夠遞歸求解前半數組最大值,遞歸求解後半數組最大值,再求解跨先後數組最大值數組

  3. 合併:將求得的值返回,比較求解到的三個最大值,返回其中的最大值bash

在個人理解中,分治法就是將一個大問題以迭代的方式不斷分解爲小問題,直到最後分解的小問題的規模小到可以以很小的代價求解出來,再將這些小問題的解綜合起來以獲得開始的大問題的解。這裏面兩個難點,一是如何經過迭代將大問題分解成易於求解的小問題,即「分」的問題,二是如何將這些小問題的解綜合或者說還原成大問題的解,即「治」。 「分」的話,很容易便想到將數組不斷地對半分,直至最終分得的小問題規模只有一個元素,那麼這個小問題的解也就是這個數字了,但問題在於「治」,這樣的分法有一個問題,就是將n個數字分紅1—n/2和n/2+1—n後(這裏爲了便於敘述,就不討論n/2不是整數的狀況了,在編寫程序時能夠用很簡單的取整函數解決這個問題),若是隻在這兩個部分再次進行分解和計算的話,就會忽略解是貫穿兩個部分的數組這樣的可能性,因此必需要在分的時候計算通過n/2的最大子數組,求解這個子數組的方式就是分別從n/2和n/2+1往左和往右對其餘元素進行相加和比較,直到找到兩邊的最大值,而後進行相加即獲得。函數

#求解中間的最大子數組
def find_max_cross(l1,left,right):
    #初始化左右兩邊的和以及解的座標
    leftmax=-float('inf')
    rightmax=-float('inf')
    ileft=-float('inf')
    iright=-float('inf')
    maxlist=[]
    sum=0
    mid=(left+right)//2
    #以mid爲右端座標往左進行尋找和的最大值
    for i in range(mid,left-1,-1):
        sum=sum+l1[i]
        if sum>leftmax:
            leftmax=sum
            ileft=i
    sum=0
    #以mid+1爲左端往右進行尋找和的最大值
    for i in range(mid+1,right+1,1):
        sum=sum+l1[i]
        if sum>rightmax:
            rightmax=sum
            iright=i
    #將兩側的結果相加,並取得相應的座標返回
    maxlist=[leftmax+rightmax,ileft,iright] 
    return maxlist
複製代碼

在找到中間的最大子數組以後,剩餘的可能爲解的子數組就只能在1–n/2和n/2+1–n這兩個區間裏了,而在這兩個區間裏的解就能夠經過不斷的迭代來求解,而後在每次迭代時比較三者的大小從而取其最大值。學習

#分治法進行迭代
def find_all(l1,left,right):
    maxlist2=[]
    #最小規模:只有一個數字的子數組
    if left==right:
        maxlist2=[l1[left],left,right]
        return maxlist2
    else:
    #左右中三部分的最大子數組信息
        lml=[]
        rml=[]
        mml=[]
        m=(left+right)//2
        #主函數find_all的迭代,迭代的同時也開始在「治」
        lml=find_all(l1,left,m)
        rml=find_all(l1,m+1,right)
        mml=find_max_cross(l1,left,right)
        #進行比較取最大值
        if lml[0]>=rml[0] & lml[0]>=mml[0]:
            return lml
        elif rml[0]>=lml[0] & rml[0]>=mml[0]:
            return rml
        else:
            return sum(mml)
複製代碼

分治法最終經過函數自身的迭代將複雜度爲n2的問題簡化到nlgn,這其中思惟的難點就是對函數迭代的理解,既要會「分」,也要能「治」,而編程的難點在於迭代過程當中的一些參數取值的範圍和邊界的細節問題ui

相關文章
相關標籤/搜索