最大子串行求和 絕妙的演算法 最大子串行和問題

2021-10-13 03:59:42 字數 3838 閱讀 3127

問題的引入

給定(可能有負數)整數序列a1, a2, a3..., an, 求這個序列中子序列和的最大值。(為方便起見,如果所有整數均為負數,則最大子串行和為0)。例如:輸入整數序列: -2, 11, 8, -4, -1, 16, 5, 0,則輸出答案為35,即從a2~a6。

這個問題之所以有吸引力,主要是因為存在求解它的很多演算法,而這些演算法的效能差異又很大。這些演算法,對於少量的輸入差別都不大,幾個演算法都能在瞬間完成,這時若花費大量的努力去設計聰明的演算法恐怕就不太值得了;但是如果對於大量的輸入,想要更快的獲取處理結果,那麼設計精良的演算法顯得很有必要。

切入正題

下面先提供乙個設計最不耗時間的演算法,此演算法很容易設計,也很容易理解,但對於大量的輸入而言,效率太低:

演算法一:

public static int maxsubsequencesum(int a)  else {

return 0; //保證最小值為0

int center = (left+right)/2;

int maxleftsum = maxsubsequencesum(a, left, center); //遞迴呼叫,求左部分的最大和

int maxrightsum = maxsubsequencesum(a, center+1, right);//遞迴呼叫,求右部分的最大和

int leftbordersum = 0, maxleftbordersum = 0;//定義左邊界子串行的和

for(int i=center; i>=left; i--) {//求左邊界的最大和(從右邊開始往左求和)

leftbordersum += a[i];

if(leftbordersum > maxleftbordersum) {

maxleftbordersum = leftbordersum;

int rightbordersum = 0, maxrightbordersum = 0;//定義右邊界子串行的和

for(int i=center+1; i<=right; i++) {//求右邊界的最大和(從左邊開始往右求和)

rightbordersum += a[i];

if(rightbordersum > maxrightbordersum) {

maxrightbordersum = rightbordersum;

//選出這三者中的最大值並返回(max3(int a, int b, int c)的實現沒有給出)

return max3(maxleftsum, maxrightsum, maxleftbordersum + maxrightbordersum);

有必要對演算法三的程式進行一些說明。遞迴過程呼叫的一般形式是傳遞輸入的陣列和左右邊界,它們界定了陣列要被處理的部分。第2~8行處理基準情況,讓遞迴呼叫有退出的機會。如果left==right,那麼只有乙個元素,並且當該元素非負時,它就是最大子串行。第11、12行執行兩個遞迴呼叫。我們可以看到,遞迴呼叫總是對小於原問題的問題進行,不過程式中的小擾動有可能破壞這個特性。14~20行,22~28行分界處左右兩邊的最大子串行和,這兩個值的和就有可能是整個序列中的最大子串行和。第31行呼叫max3方法,求出這三種情況下的最大值,該值即為整個序列的最大子串行和。

顯然,演算法三需要比設計前兩種演算法付出更多的程式設計努力,看上去前面兩種演算法的**量要比演算法三少許多,然而,程式短並不意味著程式好。測試表明,除較小的輸入量外,演算法三比前兩個演算法明顯要快。現在來分析以下演算法三的時間複雜度。

令t(n)是求解大小為n的最大子串行和問題所花費的時間。如果n=1,則演算法3執行程式第2~8行花費某個常數時間,我們稱之為乙個時間單位。於是t(1)=1,否則,程式必須執行兩個遞迴呼叫,即在14~28行之間的兩個for迴圈以及幾個小的簿記量,如10、14行。這兩個for迴圈總共接觸到a1~an中的每個元素,而在迴圈內部的工作量是常量,於是14~28行花費的時間為o(n)。從2~10、14、22和31行上的程式的工作量是常量,從而與o(n)相比可以忽略。其餘就剩下11~12行上執行的工作。這兩行求解大小為n/2的子串行問題(假設n為偶數)。因此,這兩行每行花費t(n/2)個時間單元,共花費2t(n/2)個時間單元。因此,演算法三花費的總時間為2t(n/2)+o(n)。於是我們得到方程組:

t(1) = 1

t(n) = 2t(n/2) + o(n)

為了簡化計算,我們可以用n代替上面方程中的o(n)項;由於t(n)最終還是要用大o表示,因此這麼做並不影響答案。現在,如果t(n) = 2t(n/2) + n,且t(1) = 1,那麼t(2) = 4 = 2*2;t(4) = 12 = 4*3;t(8) = 32 = 8*4;t(16) = 80 = 16*5。用數學歸納法可以證明若n=2^k,那麼t(n) = 2^k * (k+1) = n * (k+1) = n(logn + 1) = nlogn + n = o(nlogn)。即演算法三的時間複雜度為o(nlogn),這明顯小於演算法二的複雜度o(n2),因此演算法三會更快的得出結果。

這個分析假設n是偶數,否則n/2就不確定了。通過該分析的遞迴性質可知,實際上只有當n是2的冪時結果才是合理的,否則我們最終要得到大小不是偶數的子問題,方程就是無效的了。當n不是2的冪時,我們多少需要更加複雜一些的分析,但是大o的結果還是不變的。

更優秀的演算法

雖然演算法三已經足夠優秀,將時間複雜度由o(n2)降低為o(nlogn),但是,這並不是最優秀的,下面介紹針對這個問題更優秀的解法。

演算法四:

public static int maxsubsequencesum(int a) {

int maxsum = 0, thissum = 0;;

for(int i=0; i

thissum += a[i];

if(thissum > maxsum)

maxsum = thissum;

else if(thissum 

thissum = 0;

return maxsum;

很顯然,此演算法的時間複雜度為o(n),這小於演算法三中的時間複雜度o(nlogn),因此,此演算法比演算法三更快!方法固然已給出,但是要明白為什麼此方法能用,還需多加思考。

在演算法一和演算法二中,i代表子串行的起點,j代表子串行的終點。碰巧的是,我們不需要知道具體最佳的子串行在**,那麼i的使用可以從程式上被優化,因此在設計演算法的時候假設i是必需的,而且我們想改進演算法二。乙個結論是:如果a[i]是負數,那麼它不可能代表最優序列的起點,因為任何包含a[i]的作為起點的子串行都可以通過使用a[i+1]作為起點得到改進。類似的,任何負的子串行也不可能是最優子串行的字首(原理相同)。如果在內迴圈中檢測到從a[i]到a[j]的子串行的和是負數,那麼可以向後推進i。關鍵的結論是:我們不僅能夠把i推進到 i+1,而且實際上我們還可以把它一直推進到 j+1。為了看清楚這一點,我們令 p 為 i+1 和 j 之間的任意一下標。開始於下標 p 的任意子序列都不大於在下標i開始幷包含從 a[i] 到 a[p-1] 的子串行的對應的子串行,因為後面這個子串行不是負的(即j是使得從下標 i 開始,其值成為負值的序列的第乙個下標)。因此,把 i 推進到 j+1 是沒有風險的:我們乙個最優解也不會錯過。

這個演算法是許多聰明演算法的典型:執行時間是明顯的,但正確性卻不那麼容易就能看出來。對於這些演算法,正式的正確性證明(比上面分析更加正式)幾乎總是需要的;然而,及時到那時,許多人仍然還是不信服。此外,許多這類演算法需要更有技巧的程式設計,這導致更長的開發過程。不過,當這些演算法正常工作時,它們執行得很快!而我們將它們和乙個低效但容易實現的蠻力演算法通過小規模的輸入進行比較可以測試到大部分的程式原理。

該演算法的乙個附帶優點是:它值對資料進行一次掃瞄,一旦a[i]被讀入並處理,它就不再需要被記憶。因此,如果陣列在磁碟上活通過網路傳送,那麼它就可以被按順序讀入,在主存中不必儲存改陣列的任何部分。不僅如此,在任意時刻,演算法都能對它已經讀入的資料給出子串行問題的正確答案(其他演算法不具備這個特性)。具有這種特性的演算法叫做「聯機演算法」。僅需要常量空間並以線性時間執行的聯機演算法幾乎是完美的演算法。

最大子串行求和問題

給定k個整數組成的序列,連續子列 被定義為,其中 1 i j k。最大子列和 則被定義為所有連續子列元素的和中最大者。例如給定序列,其連續子列有最大的和20。輸入第1行給出正整數k 第2行給出k個整數,其間以空格分隔。在一行中輸出最大子列和。如果序列中所有整數皆為負數,則輸出0。6 2 11 4 1...

最大子串行求和問題

給定整數a1 role presentation a1a 1,a2 role presentation a2a 2,an role presentation ana n 可能有負數 求 k ija k role presentation jk iak k i jak的最大值 為方便起見,如果所有整數...

最大子串行求和整理筆記

在最大子串行的問題中,這裡有四種解法,時間複雜度由高到低,可以直觀的感受到解決問題的演算法技巧。c語言 如下 include define maxn 100 int a maxn 輸入 void input int a,int n 輸出 void output int a,int n o n n n...