最大子段和

参考资料:《计算机算法设计与分析》第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)+a j,故可以将最内层循环省去,避免重复计算,改进算法:

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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值