應用實例——最大子列和問題
題目描述:給定N個整數的序列{A1,A2,...,AN},求函數f(i,j)=max{0,ΣAk(i<=k<=j)}的最大值
算法1:
1 int MaxSubseqSum1(int A[],int N){ 2 int ThisSum,MaxSum=0; 3 int i,j,k; 4 for(i=0;i<N;i++){//i是子列左端位置 5 for(j=i;j<N;j++){//j是子列右端位置 6 ThisSum=0; 7 for(k=i;k<j;k++){//ThisSum是A[i]到A[j]的子列和8 ThisSum+=A[k]; 9 if(ThisSum>MaxSum)//如果剛得到的這個子列和更大 10 MaxSum=ThisSum;//則更新結果 11 } 12 } 13 } 14 return MaxSum; 15 }
由程序結構可知,此算法的時間復雜度T(N)=O(N3)[有三層嵌套的for循環]
算法2:
1 int MaxSubseqSum1(int A[],int N){ 2 int ThisSum,MaxSum=0; 3 int i,j; 4 for(i=0;i<N;i++){//i是子列左端位置 5 ThisSum=0; 6 for(j=i;j<N;j++){//j是子列右端位置 7 ThisSum+=A[j];//對於相同的i,不同的j,只要在j-1次循環的基礎上累加1項即可 8 if(ThisSum>MaxSum)//如果剛得到的這個子列和更大 9 MaxSum=ThisSum;//則更新結果 10 } 11 }12 } 13 return MaxSum; 14 }
由程序結構可知,此算法的時間復雜度T(N)=O(N2)[有兩層嵌套的for循環]
算法3:分而治之
算法思路:將一個比較大而復雜的問題切分成比較小的模塊,然後分頭解決,最後把結果合並起來。
工作流程:[假設待解決的問題放在一個數組裏面]
- 第一步:劃分:按照平衡子問題的原則,將序列(a1,a2,...,an)劃分成長度相同的兩個序列(a1,...,an/2)和(an/2+1,...,an),則會出現一下三種情況:
- a1,...,an的最大字段和=a1,...,an/2的最大字段和①
- a1,...,an的最大字段和=an/2+1,...,an的最大字段和②
- a1,...,an的最大字段和=Σak(i<=k<=j),且1<=i<=n/2,n/2+1<=j<=n③
- 第二步:求解子問題:對於劃分階段的情況①和②可遞歸求解,情況③需要分別計算左右兩端的最大子列和,然後兩個之和為情況③的最大子列和。
- 第三步:合並:比較在劃分階段三種情況下的最大子列和,取三者關系中的較大者為原問題的解。
例:給定一個數組[4,-3,5,-2,-1,2,6,-2]
先從中間將其一分為二
然後遞歸地先解決左半邊,繼續將其一分為二
繼續地遞歸到左半邊,繼續分
針對最後的一次劃分,左邊的最大子列和為4,右邊最大子列和為-3,小於左邊,會返回0,則跨越邊界的最大子列和為4。依此類推,得到最終的劃分結果為:
int MaxSubseqSum3(int a[],int left,int right){//求序列a[left]~a[right]的最大子列和 int sum=0,midSum=0,leftSum=0,rightSum=0; int center,s1,s2,lefts,rights; if(left==right)//如果序列長度為1,直接求解 sum=a[left]; else{ center=(left+right)/2;//劃分 leftSum=MaxSubseqSum3(a,left,center);//對應情況①,遞歸求解 rightSum=MaxSubseqSum3(a,center,right);//對應情況②,遞歸求解 s1=0;lefts=0;//以下對應情況③,先求解s1 for(int i=center;i>=left;i--){ lefts+=a[i]; if(lefts>s1) s1=lefts; } s2=0;rights=0;//再求解s2 for(int j=center+1;j<=right;j++){ rights+=a[j]; if(rights>s2) s2=rights; } midSum=s1+s2;//計算情況③的最大子列和 if(midSum<leftSum)//合並解,取較大者 sum=lefstSum; else sum=midSum; if(sum<rightSum) sum=rightSum; } return sum; }
劃分之後,每個程序執行的數據規模是N/2,對應劃分得到的情況①和情況②,需要分別進行遞歸求解,對應情況③,兩個並列for循環的時間復雜性是O(N),所以存在如下的遞推式:
- T(N)=1 當N=1時;
- T(N)=2T(N/2)+c·N 當N>1時;
T(N)=2T(N/2)+c·N=2[2T(N/4)+c·N/2]+c·N
=22T(N/22)+2c·N
=2kO(1)+ck·N,其中N/2k=1
=O(Nlog2N)
由程序結構可知,此算法的時間復雜度T(N)=O(Nlog2N)
算法4:在線處理
“在線”的意思是指每輸入一個數據就進行即時處理,在任何一個地方中止輸入,算法都能正確給出當前的解。
1 int MaxSubseqSum4(int A[],int N){ 2 int ThisSum,MaxSum=0; 3 int i; 4 ThisSum=MaxSum=0; 5 for(i=0;i<N;i++){ 6 ThisSum+=A[i];//向右累加 7 if(ThisSum>MaxSum) 8 MaxSum=ThisSum;//發現更大和則更新當前結果 9 else if(ThisSum<0)//如果當前子列和為負 10 ThisSum=0;//則不可能使得後面的部分和增大,拋棄之 11 } 12 return MaxSum; 13 }
由程序結構可知,此算法的時間復雜度T(N)=O(N)[只有一個for循環,而且if-else的復雜度都為常數]
復雜度越小,理解起來越困難
例:
①第一個數字為-1,由於它不能使後面的部分增大,因此將ThisSum重置為0,MaxSum=0
②MaxSum=3,ThisSum=3
③ThisSum=1>0,不會被重置為0
④ThisSum=5,MaxSum=5,最大和更新
⑤ThisSum=-1<0,被拋棄,將ThisSum重置為0
⑥ThisSum=1,MaxSum=5
⑦ThisSum=7,MaxSum=7,最大和更新
⑧ThisSum=6,MaxSum=7,最大和未改變
所以得到的最大子列和為7。
算法 | 1 | 2 | 3 | 4 |
時間復雜度 | O(N3) | O(N2) | O(NlogN) | O(N) |
N=10 | 0.00103 | 0.00045 | 0.00066 | 0.00034 |
N=100 | 0.47015 | 0.01112 | 0.00486 | 0.00063 |
N=1000 | 448.77 | 1.1233 | 0.05843 | 0.00333 |
N=10000 | NA | 111.13 | 0.68631 | 0.03042 |
N=100000 | NA | NA | 8.0113 | 0.29832 |
應用實例——最大子列和問題