分治法求最大子數組問題
1、最大子數組問題:
在長度爲n的一維數組中,求一個元素相加後和最大的連續子數組。
2、暴力求解
求得這樣的連續數組,暴力求解就是從第一個元素開始,分別計算1個,2個,3個……直到n個元素的和並進行比較,再從第2個元素開始,計算n-1個數組的和……最後計算第n個元素開始的數組,也就只有它本身一個元素的數組,這樣計算下來算法的複雜度在 這個數量級,具體python實現的代碼如下:
#暴力求解最大子數組
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 list_result
3、分治法求解
在我的理解中,分治法就是將一個大問題以迭代的方式不斷分解爲小問題,直到最後分解的小問題的規模小到能夠以很小的代價求解出來,再將這些小問題的解綜合起來以得到開始的大問題的解。這裏面兩個難點,一是如何通過迭代將大問題分解成易於求解的小問題,即“分”的問題,二是如何將這些小問題的解綜合或者說還原成大問題的解,即“治”。
“分”的話,很容易便想到將數組不斷地對半分,直至最終分得的小問題規模只有一個元素,那麼這個小問題的解也就是這個數字了,但問題在於“治”,這樣的分法有一個問題,就是將n個數字分成1—n/2和n/2+1—n後(這裏爲了便於敘述,就不討論n/2不是整數的情況了,在編寫程序時可以用很簡單的取整函數解決這個問題),如果只在這兩個部分再次進行分解和計算的話,就會忽略解是貫穿兩個部分的數組這樣的可能性,所以必須要在分的時候計算經過n/2的最大子數組,求解這個子數組的方式就是分別從n/2和n/2+1往左和往右對其他元素進行相加和比較,直到找到兩邊的最大值,然後進行相加即得到。
求解中間的最大子數組的python實現如下。
#求解中間的最大子數組
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這兩個區間裏了,而在這兩個區間裏的解就可以通過不斷的迭代來求解,然後在每次迭代時比較三者的大小從而取其最大值。
具體的python代碼實現如下。
#分治法進行迭代
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 mml
4、總結
分治法最終通過函數自身的迭代將複雜度爲 的問題簡化到 ,這其中思維的難點就是對函數迭代的理解,既要會“分”,也要能“治”,而編程的難點在於迭代過程中的一些參數取值的範圍和邊界的細節問題。