1. 程式人生 > >基礎演算法--B最大欄位和

基礎演算法--B最大欄位和

Description

Given a sequencea[1],a[2],a[3]......a[n], your job is to calculate the max sum of asub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is6 + (-1) + 5 + 4 = 14.

Input

The first lineof the input contains an integer T(1<=T<=20) which means the number oftest cases. Then T lines follow, each line starts with a numberN(1<=N<=100000), then N integers followed(all the integers are between-1000 and 1000).

Output

For each testcase, you should output two lines. The first line is "Case #:", #means the number of the test case. The second line contains three integers, theMax Sum in the sequence, the start position of the sub-sequence, the endposition of the sub-sequence. If there are more than one result, output thefirst one. Output a blank line between two cases.

Sample Input

2

5 6 -1 5 4 -7

7 0 6 -1 1 -6 7-5

Sample Output

Case 1:

14 1 4

Case 2:

7 1 6

最大欄位和一直都有很多做法:

1.      列舉演算法

簡單的列舉演算法當然可以解決這個問題,列舉所有可能的起始下標(i=1,2,3……,n)和終止下標(j=1,2,3……,n),累加各種情況的元素和,並從中選出最大的欄位和。

2.      二分策略演算法

如果將所給的序列a[1:n]分為長度相等的兩段a[1: (n/2)]]和b[(n/2)+1:n],分別求出這兩段的最大欄位和,則a[1:n]的最大欄位和有3種情形。

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

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

(3),a[1:n] 的最大子段和為a[i:j],()

情形(1)和(2)可遞迴求得。

對於情形(3),序列中的元素a[(n/2)]與a[(n/2)+1]一定在最大子段中。因此可以算出a[i: (n/2)]的最大值S1;並計算出a[(n/2)+1:j]中的最大值S2。則S1+S2即為出現情況(3)時的最優值。

據此可設計最大子段和的分治演算法求出最大子段。由於子問題不獨立,不同於一般的二分治演算法,這裡演算法的實質是“三”分治。

intmax_sum3(int a[],int n)

{

return max_sub_sum(a,1,n);

}

intmax_sub_sum(int a[],int left,int right)

{

intcenter,i,j,sum,left_sum,right_sum,s1,s2,lefts,rights;

if(left==right)

           if(a[left]>0)

                    return a[left];

           else

                    return 0;

else

{

           center=(left+right)/2;//二分中心位置

           left_sum=max_sub_sum(a,left,center);//求左部最大子段和

           right_sum=max_sub_sum(a,center+1,right);//求右部最大子段和

           s1=0;//處理情形3

           lefts=0;

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

           {

                    lefts=lefts+a[i];

                    if(lefts>s1)

                             s1=lefts;

           }

           s2=0;

           rights=0;

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

           {

                    rights=rights+a[i];

                    if(rights>s2)

                             s2=rights;

           }

           if(s1+s2<left_sum&&right_sum<left_sum)//情形1

                    return left_sum;

           if(s1+s2<right_sum)//情形2

                    return right_sum;

           return s1+s2;//情形3

}

}

3.      動態規劃演算法

方法2中由於分解後子問題不獨立,情形3中重複計算較多,沒有充分利用前期的計算結果。而動態規劃的特長就是解決分解的子問題不獨立的情況。用動態規劃就是通過開闢儲存空間,儲存各個子問題的計算結果,從而避免重複計算。並且動態規劃有很強的階段遞推思想,用前一階段儲存的計算結果遞推後一階段的結果。

記sum[i]為a[1]~a[i]的最大子段和,記this_sum[i]為當前子段和。

this_sum[i]從i=1開始計算,當this_sum[i-1]>=0時,前面子段的和對總和有作用,所以要累加當前元素的值;當this_sum[i-1]<=0時,則開始重新累加。

初值:this_sum[0]=0;i=1,2……,n時

this_sum[i]=this_sum[i-1]+a[i]   當this_sum[i-1]>=0

this_sum[i]=a[i]       當this_sum[i-1]<0

相應的sum[i]的遞推定義式即:a[0]=0,i=1,2……,n時

sum[i]=sum[i-1]      當this_sum[i]<=sum[i-1]

sum[i]=this_sum[i]    當this_sum[i]>sum[i-1]

sum[i]在記錄a[1]~a[i]的最大子段和。

 由此思想得出的完全程式碼:

#include <stdio.h> 

#include <stdlib.h>

#include <string.h>

int a[100001];

int maxSub(int *a,int n)  //求最大子段和函式

int i=0, max=0,max_pos=0;

   int si_1=0,si=0;

   int *p = (int *)malloc(n*sizeof(int));//通過p來記錄this_sum[i-1]的狀態

   if (p==NULL) 

       return -1; 

   max =si_1 = a[0];  //初始化max,sum[i-1]

   p[0] = 0;  //p[i]=0表示this_sum[i-1]<0

   for (i=1; i<n; i++)

         { 

       if (si_1<0){    //sum[i-1]<0的情況

           p[i] = 0; 

           si = a[i];  //si即sum[i]

       } else{ 

           p[i] = 1; 

           si = si_1+a[i];  //累加

       } 

       si_1 = si; 

       if (si>max){    //記錄最大子段和

           max = si; 

           max_pos = i;  //記錄最大子段終點

       } 

   } 

   for (i=max_pos; i>=0; i--) 

       if (p[i]==0)  //尋找最大欄位起點

           break; 

   printf("%d %d %d\n", max,i+1,max_pos+1); 

   free(p); 

    p= NULL; 

   return max; 

int main() 

   int n,i,m,j;

         scanf("%d",&m);

         getchar();

         for(i=0;i<m;i++)

         {

                   scanf("%d",&n);

                   for(j=0;j<n;j++)

                            scanf("%d",&a[j]);//輸入數列

                   printf("Case%d:\n",i+1);

   maxSub(a, n);

         if((i!=m-1))

         printf("\n");

         memset(a,0,sizeof(a));

         }

   return 0; 

}