從最大子段和問題看演算法的優化問題

2021-04-02 08:04:53 字數 3344 閱讀 1751

問題的提出:

給定n個整數(可能為負數)組成的序列a[1],a[2],a[3],…,a[n],求該序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。當所給的整均為負數時定義子段和為0,依此定義,所求的最優值為

max,1<=i<=j<=n

例如,當(a1,a2,a3,a4,a4,a6)=(-2,11,-4,13,-5,-2)時,最大子段和為20

顯然對於這樣的演算法,對於愛寫程式的朋友肯定不難。。。。

讓我們看看他們寫出來的程式吧!^_^

public class subsegment

}retrun sum;

}

}
大家看得怎麼樣呢?果然是程式快手。。。大家看看,思路看清楚吧。在前面的兩個迴圈是內部是計算子段(i,j)的子段和,再和當前的最大值比較,如果比當前的最大子段和大,那剛更新該值。好乙個思路清晰的程式,但是它的時間複雜度怎麼樣呢,我們算一下,發現t(n)=o(n^3)。哇……可能大家看了程式不覺得很嚇人,但這樣時間複雜度也夠嚇人的,呵呵!

大家看得怎麼樣呢?果然是程式快手。。。大家看看,思路看清楚吧。在前面的兩個迴圈是內部是計算子段(i,j)的子段和,再和當前的最大值比較,如果比當前的最大子段和大,那剛更新該值。好乙個思路清晰的程式,但是它的時間複雜度怎麼樣呢,我們算一下,發現t(n)=o(n^3)。哇……可能大家看了程式不覺得很嚇人,但這樣時間複雜度也夠嚇人的,呵呵!

大家有沒有發現上面的程式多做了重複的事情呢!認真想一下,想出來了再看下面的程式喔!

我對陣列進行累加的時候,特別是順序累的時候,我們是能夠直接利用前面的累加結果的,如果s[j]表示前面j項的和,那麼就有s[j+1]=s[j]+a[j+1],利用這一點,我們可以把上面的程式行進優化了。

改進後的演算法如下:

public class subsegment

public static int maxsum(int a)

int sum=0;

for(int i=0;iint thissum=0;

for(int j=0;jthissum+=a[j];   //累加,本次利用上次的結果

if(thissum>sum)

sum=thissum;

return sum;

哈哈,怎麼樣,很佩服那些能看出來的這點毛病的朋友吧。我們來看看它的時間複雜度,t(n)=o(n^2)。謝天謝地,時間複雜度終於降低了。

其實上面只是利了乙個小小的技巧,把時間複雜度降低,節省了計算時間。下面我們用分治演算法來處理這一問題,情況又是怎樣呢?如果沒有學過分治演算法,那可要花多點工夫想一下拉。

最大子段和問題的分治演算法

針對最大子段和這個具體問題本身的結構,我們還可以從演算法設計的策略上對上述o(n^2)計算時間演算法進行更進一步的改進。從問題的解結構也可以看出,它適合於用分治法求解。

如果將所給的序列a[1:n]分為長度相等的兩段a[1:n/2]和a[n/2+1:n],分別求出這兩段的最大子段和,則a[1:n]的最大子段和有三種情況:

(1) a[1:n]的最大子段和與a[1:n/2]的最大子段和相同

(2) a[1:n]的最大子段和與a[n/2+1:n]的最大子段和相同

(3) a[1:n]的最大子段和為a[i]+…+a[j],並且1<=i<=n/2,n/2+1<=j<=n。

對於(1)和(2)兩種情況可遞迴求得,但是對於情況(3),容易看出a[n/2],a[n/2+1]在最大子段中。因此,我們可以在a[1:n/2]中計算出s1=max(a[n/2]+a[n/2-1]+…+a[i]),0<=i<=n/2,並在a[n/2+1:n]中計算出s2= max(a[n/2+1]+a[n/2+2]+…+a[i]),n/2+1<=i<=n。則s1+s2為出現情況(3)的最大子段和。據此可以設計出最大子段和問題的分治演算法如下:

public class subsegment

private static int maxsubsum(int a,int left,int right)

int sum=0;

if(left==right) sum=a[left]>0?a[left]:0;

else

int center=(left+rigth)/2;

int leftsum=maxsubsum(a,left,center);

int rightsum=maxsubsum(a,center+1,right);

int s1=0;

int lefts=0;

for(int i=center;i>=left;i--)

lefts+=a[i];

if(lefts>s1)s1=lefts;

int s2=0;

int rights=0;

for(int i=center;i<=right;i++)

rights+=a[i];

if(rights>s2)s2=rights;

sum=s1+s2;

if(sumif(sumretrun sum;

public static int maxsum(int a)

return maxsubsum(a,0,a.length);

該演算法所需的計算時間t(n)可滿足典型的分治演算法遞迴分式

t(n)=2t(n/2)+o(n),由此遞迴方程可知,t(n)=o(nlogn)。

我們簡直不可相信,分治演算法還有這把拿手好戲。。。感覺怎麼樣呢?再來看乙個讓我們吃驚的演算法吧。好,讓我們用動態規劃演算法來處理這個問題。

最大子段和問題的動態規劃演算法

在對於上述分治演算法的分析中我們注意到,若記b[j]=max(a[i]+a[i+1]+..+a[j]),其中1<=i<=j,並且1<=j<=n。則所求的最大子段和為max b[j],1<=j<=n。

由b[j]的定義可易知,當b[j-1]>0時b[j]=b[j-1]+a[j],否則b[j]=a[j]。故b[j]的動態規劃遞迴式為:

b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n。

據此,可設計出求最大子段和問題的動態規劃演算法如下:

public class subsegment

public static int maxsum(int a)

int sum=0;

int b=0;

for(int i=0;iif(b>0) b+=a[i];

else b=a[i];

if(b>sum)sum=b;

return sum;

上述演算法只用到了o(n)的時間複雜度和o(1)的空間複雜度。

我們從是最大子段和問題的優化過程中可以看出,我們把乙個演算法從o(n^2)優化到o(nlogn),再到o(n)是多麼不容易的一件事情,所以我們在寫演算法的時候,盡量多想想為什麼,然後根據我們的演算法知識,計算一下採用某一種演算法實現的時間複雜度,從而採用一種最優的演算法。

從最大子段和問題看演算法的優化問題

已有 114 次閱讀 2009 06 14 23 29 問題的提出 給定n個整數 可能為負數 組成的序列a 1 a 2 a 3 a n 求該序列如a i a i 1 a j 的子段和的最大值。當所給的整均為負數時定義子段和為0,依此定義,所求的最優值為 max,1 i j n 例如,當 a1,a2,...

從最大子段和問題看演算法的優化問題

問題的提出 給定n個整數 可能為負數 組成的序列a 1 a 2 a 3 a n 求該序列如a i a i 1 a j 的子段和的最大值。當所給的整均為負數時定義子段和為0,依此定義,所求的最優值為 max,1 i j n 例如,當 a1,a2,a3,a4,a4,a6 2,11,4,13,5,2 時,...

演算法筆記 最大子段和問題

演算法筆記 1.非連續最大子段和 如果不全為負數,最大子段和所有大於等於0的元素的和 如果全為負數,最大子段和為最大的負數。2.連續最大子段和 無長度限制 例題 洛谷p1115最大子段和 includeusing namespace std define ll long long intmain c...