最大子段和

来源:互联网 发布:cs1.6弹道优化 编辑:程序博客网 时间:2024/06/06 07:06

参考资料:《计算机算法设计与分析》第3版:Page59-64,王晓东 著

一、问题描述:

给定由n个整数(可能为负整数)组成的序列a1, a2, ..., an,求该序列的形如(s(i,j)=ai + ai+1 + ... aj, 1<=i<=j<=n)的子段和的最大值。当所有整数均为负整数时定义其最大子段和为0。最优值为:max{0, max{s(i,j), 1<=i<=j<=n}}。

二、简单算法:

用数组a[],存储给定的n个整数a1, a2, ..., an

int max_sum(int n,int *a,int &besti,int &bestj){int sum=0;besti=bestj=-1;for(int i=1;i<=n;++i){for(int j=i;j<=n;++j){int this_sum=0;for(int k=i;k<=j;++k){this_sum+=a[k];}if(this_sum>sum){sum=this_sum;besti=i;bestj=j;}}}return sum;}
其中算法时间复杂度为O(n^3)。事实上,s(i,j)=s(i,j-1)+aj,故可以将最内层循环省去,避免重复计算,改进算法:

int max_sum_optimize1(int n,int *a,int &besti,int &bestj){int sum=0;besti=bestj=-1;for(int i=1;i<=n;++i){int this_sum=0;for(int j=i;j<=n;++j){this_sum+=a[j];if(this_sum>sum){sum=this_sum;besti=i;bestj=j;}}}return sum;}
改进后算法时间复杂度为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]的最大子段和为s(i,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{0, max{s(i,n/2), 1<=i<=n/2}},并在在a[n/2+1:n]中可计算s2=max{0, max{s(n/2+1,j), n/2+1<=j<=n}},则s1+s2即为(3)时的最优值。故最大子段和的分治算法如下:

int max_sum_dac(int *a,int left,int right){int sum=0;if(left==right){sum=a[left]>0?a[left]:0;}else{int center=(left+right)/2;int left_sum=max_sum_dac(a,left,center);int right_sum=max_sum_dac(a,center+1,right);int s1=0,s2=0,temp=0;for(int i=center;i>=left;--i){temp+=a[i];if(temp>s1){s1=temp;}}temp=0;for(int i=center+1;i<=right;++i){temp+=a[i];if(temp>s2){s2=temp;}}sum=s1+s2;if(sum<left_sum){sum=left_sum;}if(sum<right_sum){sum=right_sum;}}return sum;}int max_sum_optimize2(int n,int *a){return max_sum_dac(a,1,n);}

此算法时间复杂度为O(nlogn)。

四、动态规划算法:

若记b[j]=max{s(i,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]=max{b[j-1]+a[j], a[j]}, 1<=j<=n。

int max_sum_optimize3(int n,int *a){int sum=0,b=0;for(int i=1;i<=n;++i){if(b>0){b+=a[i];}else{b=a[i];}if(b>sum){sum=b;}}return sum;}
上述算法时间复杂度为O(n),空间复杂度为O(n)(存放数组a[])。

五、推广1-最大子矩阵和:

给定一个m行n列的整数矩阵A,试求矩阵A的一个子矩阵,使其各元素之和为最大。最大子矩阵和是最大子段和问题向二维的推广。用二维数组a[1:m][1:n]表示矩阵A。子数组a[i1:i2][j1:j2]表示左上角和右下角行列左边分别为(i1,j1)、(i2,j2)的子矩阵,其各元素之和记为:

s(i1,i2,j1,j2)=(a[i1][j1]+a[i1+1][j1]+...+a[i2][j1])+(a[i1][j1+1]+a[i1+1][j1+1]+...+a[i2][j1+1])+...+(a[i1][j2]+a[i1+1][j2]+...+a[i2][j2])

最大子矩阵和问题的最优值为:max{s(i1,i2,j1,j2), 1<=i1<=i2<=m, 1<=j1<=j2<=n}。直接枚举法时间复杂度为O(m^2 * n^2)。但有:

max{s(i1,i2,j1,j2), 1<=i1<=i2<=m, 1<=j1<=j2<=n}=max{max{s(i1,i2,j1,j2), 1<=j1<=j2<=n}, 1<=i1<=i2<=m}}=max{t(i1,i2), 1<=i1<=i2<=m}

其中:t(i1,i2)=max{s(i1,i2,j1,j2), 1<=j1<=j2<=n}。设b[j]=a[i1][j]+a[i1+1][j]+...+a[i2][j],则t(i1,i2)=max{b[j1]+b[j1+1]+...+b[j2], 1<=j1<=j2<=n},此问题即为一维最大子段和问题。故借助一维最大子段和问题的动态规划算法max_sum_optimize3,可设计出最大子矩阵和的动态规划算法max_sum2如下:

int max_sum2(int m,int n,int **a){int sum=0;int *b=new int[n+1];for(int i=1;i<=m;++i){for(int k=1;k<=n;++k){b[k]=0;}for(int j=i;j<=m;++j){for(int k=1;k<=n;++k){b[k]+=a[j][k];}int temp=max_sum_optimize3(n,b);if(temp>sum){sum=temp;}}}return sum;}

此算法的时间复杂度为O(m^2 * n)。

六、推广2-最大m子段和问题:

给定由n个整数(可能为负整数)组成的序列a1, a2, ..., an,以及一个正整数m且1<=m<=n,要求确定序列a1, a2, ..., an的m个不相交子段,使这m个子段的总和达到最大。最大m子段和是最大子段和问题在子段个数上的推广。

设b(i,j)表示数组a的前j项中i个子段和的最大值,且第i个子段含a[j],1<=i<=m, i<=j<=n,则所求的最优值为:max{b(i,j), m<=j<=n}。计算b(i,j)的递归式为:b(i,j)=max{b(i,j-1)+a[j], max{b(i-1,t)+a[j], i-1<=t<j}}, 1<=i<=m, i<=j<=n。其中,b(i,j-1)+a[j]表示第i个子段含a[j-1],max{b(i-1,t)+a[j], i-1<=t<j}表示第i个子段仅含a[j]。初始值,b(0,j)=0, 1<=j<=n; b(i,0)=0, 1<=i<=m。最大m子段和问题的动态规划算法如下:

int max_sum_m(int m,int n,int *a){if(n<m||m<1){return 0;}int **b=new int*[m+1];for(int i=0;i<=m;++i){b[i]=new int[n+1];}for(int i=0;i<=m;++i){b[i][0]=0;}for(int j=1;j<=n;++j){b[0][j]=0;}for(int i=1;i<=m;++i){b[i][i]=b[i-1][i-1]+a[i];for(int j=i+1;j<=n;++j){b[i][j]=b[i][j-1]+a[j];for(int k=i-1;k<j;++k){if(b[i][j]<b[i-1][k]+a[j]){b[i][j]=b[i-1][k]+a[j];}}}}int sum=0;for(int j=m;j<=n;++j){if(sum<b[m][j]){sum=b[m][j];}}for(int i=0;i<=m;++i){delete[] b[i];}delete[] b;return sum;}

此时算法时间复杂度为O(mn^2),空间复杂度为O(mn)。由b(i,j)的定义式可知,在计算b(i,j)时只用到b(i-1,t), i-1<=t<j和b(i,j-1):故算法中只需保存数组b的当前行,而不必存储整个数组;max{b(i-1,t)+a[j], i-1<=t<j的值可以在计算第b(i-1,j)时预先计算并保存起来,计算b(i,j)时不必重新计算,节省了计算时间和空间;另外,最后要求max{b(m,j), m<=j<=n},故在求b(i,j), 1<=i<=m时,只需求b(i,t), i<=t<=n-m+i,而b(i,t), n-m+i+1<=t<=n不必计算。改进后算法如下:

int max_sum_m_optimize(int m,int n,int *a){if(n<m||m<1){return 0;}int *b=new int[n+1];int *c=new int[n+1];for(int j=0;j<=n-m;++j){c[j]=0;}b[0]=0;for(int i=1;i<=m;++i){b[i]=b[i-1]+a[i];int max=b[i];for(int j=i+1;j<=n-m+i;++j){b[j]=b[j-1]>c[j-1]?b[j-1]+a[j]:c[j-1]+a[j];c[j-1]=max;if(max<b[j]){max=b[j];}}c[n-m+i]=max;}int sum=0;for(int j=m;j<=n;++j){if(sum<b[j]){sum=b[j];}}return sum;}

此时,算法复杂度为O(m(n-m)),空间复杂度为O(n),当m或(n-m)为常数时,算法复杂度为O(n),空间复杂度为O(n)。

七、测试代码:

#include<cstdio>#include<cstdlib>#include<ctime>#define random(x) (rand()%(x)-(x)/2)int max_sum(int n,int *a,int &besti,int &bestj);int max_sum_optimize1(int n,int *a,int &besti,int &bestj);int max_sum_dac(int *a,int left,int right);int max_sum_optimize2(int n,int *a);int max_sum_optimize3(int n,int *a);int max_sum2(int m,int n,int **a);int max_sum_m(int m,int n,int *a);int max_sum_m_optimize(int m,int n,int *a);int main(int argc,char *argv[]){const int SIZE=20;srand(time(0));int a1[SIZE+1];for(int i=1;i<=SIZE;++i){a1[i]=random(101);printf("%d,",a1[i]);}printf("\n");int besti,bestj,sum;sum=max_sum(SIZE,a1,besti,bestj);printf("%d,%d,%d\n",sum,besti,bestj);sum=max_sum_optimize1(SIZE,a1,besti,bestj);printf("%d,%d,%d\n",sum,besti,bestj);sum=max_sum_optimize2(SIZE,a1);printf("%d\n",sum);sum=max_sum_optimize3(SIZE,a1);printf("%d\n\n",sum);sum=max_sum_m(1,SIZE,a1);printf("%d\n",sum);sum=max_sum_m(SIZE/3,SIZE,a1);printf("%d\n",sum);sum=max_sum_m(SIZE/2,SIZE,a1);printf("%d\n",sum);sum=max_sum_m(SIZE,SIZE,a1);printf("%d\n\n",sum);sum=max_sum_m_optimize(1,SIZE,a1);printf("%d\n",sum);sum=max_sum_m_optimize(SIZE/3,SIZE,a1);printf("%d\n",sum);sum=max_sum_m_optimize(SIZE/2,SIZE,a1);printf("%d\n",sum);sum=max_sum_m_optimize(SIZE,SIZE,a1);printf("%d\n\n",sum);int **a2;a2=new int*[SIZE+1];for(int i=1;i<=SIZE;++i){a2[i]=new int[SIZE+1];}for(int i=1;i<=SIZE;++i){for(int j=1;j<=SIZE;++j){a2[i][j]=random(101);}}sum=max_sum_m_optimize(1,SIZE,a2[1]);printf("%d\n",sum);sum=max_sum_optimize3(SIZE,a2[1]);printf("%d\n\n",sum);sum=max_sum2(1,SIZE,a2);printf("%d\n",sum);sum=max_sum2(SIZE/3,SIZE,a2);printf("%d\n",sum);sum=max_sum2(SIZE/2,SIZE,a2);printf("%d\n",sum);sum=max_sum2(SIZE,SIZE,a2);printf("%d\n",sum);for(int i=1;i<=SIZE;++i){delete[] a2[i];}delete[] a2;return 0;}

0 0
原创粉丝点击