1. 程式人生 > >應用實例——最大子列和問題

應用實例——最大子列和問題

[] str else -s 給定 復雜 lin wid 工作流

題目描述:給定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


應用實例——最大子列和問題