最大子段和算法详解

问题描述

给定由n个整数(可能为负整数)组成的序列 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,,an,求该序列形如 ∑ k = i j a k \sum_{k=i}^j a_k k=ijak的子段和的最大值。当所有整数均为负整数时定义其最大子段和为0。依此定义,所求的最优值为 m a x { 0 , m a x ∑ k = i j a k ( 1 ≤ i ≤ j ≤ n ) } max\{ 0,max \sum_{k=i}^j a_k \left(1\le i \le j \le n\right)\} max{0,maxk=ijak(1ijn)}
例如当 ( a 1 , a 2 , a 3 , a 4 , a 5 , a 6 ) = ( − 2 , 11 , − 4 , 13 , − 5 , − 2 ) \left(a_1,a_2,a_3,a_4,a_5,a_6\right)=\left(-2,11,-4,13,-5,-2\right) (a1,a2,a3,a4,a5,a6)=(2,11,4,13,5,2)时,最大子段和为 ∑ k = 2 4 a k = 20 \sum_{k=2}^4 a_k=20 k=24ak=20

简单算法

最大子段和问题简单算法改进后的代码 。对所有的 ( i, j )对,顺序求和ai + … + aj,并比较出最大的和,标识该子段的首末位置。时间复杂度为 O ( n 2 ) O(n^2) O(n2)

#include <iostream>
using namespace std;
int MaxSum(int n,int *a,int &besti,int &bestj)
{
	int sum = 0;
	for(int i = 0;i < n;i++)
	{
		int thissum = 0;
		for(int j = i;j < n;j++)
		{
			thissum += a[j];
			if(thissum > sum)
			{
				sum = thissum;
				besti = i;
				bestj = j;
			}
		}
	}
	return sum;
}
int main()
{
	int n = 6;
	int a[] = {-2,11,-4,13,-5,-2};
	int besti;
	int bestj; 
	cout<<"最大子段和为:"<<MaxSum(n,a,besti,bestj)<<endl;
	cout<<"起始位置:"<<besti<<endl<<"终止位置:"<<bestj;
	return 0;	
} 

递归算法

针对最大子段和这个具体问题本身的结构,还可以从算法设计的策略上对上述 O ( n 2 ) O(n^2) O(n2)计算时间算法加以更深刻的改进。从这个问题的解的结构可以看出,它适合用分治法求解。
如果将所给的序列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]的最大子段和为 ∑ k = i j a k \sum_{k=i}^j a_k k=ijak,且 1 ≤ i ≤ n / 2 , n / 2 + 1 ≤ j ≤ n 1\le i\le n/2,n/2+1\le j\le n 1in/2,n/2+1jn

第1和第2种情形可递归求得。对于第三种情况,容易看出,a[n/2]与a[n/2+1]在最优子序列中。因此,可以在a[1:n/2]中计算出 s 1 = m a x ∑ k = i n / 2 a [ k ] ( 1 ≤ i ≤ n / 2 ) s_1=max \sum_{k=i}^{n/2} a[k] (1\le i\le n/2) s1=maxk=in/2a[k](1in/2),并在a[n/2+1:n]中计算出 s 2 = m a x ∑ k = n / 2 + 1 i a [ k ] ( n / 2 + 1 ≤ i ≤ n ) s_2=max \sum_{k=n/2+1}^i a[k] (n/2+1\le i\le n) s2=maxk=n/2+1ia[k](n/2+1in),则 s 1 + s 2 s_1+s_2 s1+s2即为出现情形3时的最优质。据此可设计出求最大子段和的分治算法如下,该算法的时间复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)

#include<iostream>
using namespace std;
int MaxSubSum(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 leftsum = MaxSubSum(a,left,center);
		int rightsum = MaxSubSum(a,center+1,right);
		int s1 = 0;
		int lefts = 0;
		for(int i = center;i >= left;i--)
		{
			lefts += a[i];
			if(lefts > s1)
				s1 = lefts;
		}
		int s2 = 0;
		int rights = 0;
		for(int i = center+1;i <= right;i++)
		{
			rights += a[i];
			if(rights > s2)
				s2 = rights;
		}
		sum = s1 + s2;
		if(sum < leftsum)
			sum = leftsum;
		if(sum <rightsum)
			sum = rightsum; 
	}
	return sum;
}
int MaxSum(int n,int *a)
{
	return MaxSubSum(a,0,n-1);
}
int main()
{
	int n = 6;
	int a[] = {-2,11,-4,13,-5,-2};
	cout<<"最大子段和为:"<<MaxSum(n,a)<<endl;
	return 0;
}

动态规划算法

对上述分治算法的分析中注意到,若记 b [ i ] = m a x { ∑ k = i j a [ k ] } ( 1 ≤ i ≤ j , 1 ≤ j ≤ n ) b[i]=max\{\sum_{k=i}^j a[k]\} \left(1\le i\le j,1\le j\le n\right) b[i]=max{k=ija[k]}(1ij,1jn),则所求的最大子段和为 m a x 1 ≤ i ≤ j ≤ n ∑ k = i j a [ k ] = m a x 1 ≤ j ≤ n m a x 1 ≤ i ≤ j ∑ k = i j a [ k ] = m a x 1 ≤ j ≤ n b [ j ] max_{1\le i\le j\le n} \sum_{k=i}^j a[k]=max_{1\le j\le n} max_{1\le i\le j} \sum_{k=i}^j a[k]=max_{1\le j\le n} b[j] max1ijnk=ija[k]=max1jnmax1ijk=ija[k]=max1jnb[j]
由b[j]的定义易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。由此可得计算b[j]的动态规划递归式为 b [ j ] = m a x { b [ j − 1 ] + a [ j ] , a [ j ] } , 1 ≤ j ≤ n b[j]=max\{b[j-1]+a[j],a[j]\},1\le j\le n b[j]=max{b[j1]+a[j],a[j]},1jn
因此,可设计出求最大子段和的动态规划算法如下:

#include<iostream>
using namespace std;
int MaxSum(int n,int *a,int &besti,int &bestj)
{
	//b表示的是以元素a[i]为结尾的最大子段和 
	int sum = -1, b = 0,flag;
	for(int i = 0;i < n;i++)
	{
		if(b > 0)	b += a[i];
		else 
		{
			b = a[i];
			flag=i;	
		}
		if(b > sum)
		{
			sum = b;
			besti=flag;
			bestj=i;
		}
		
	}
	return sum; 
}
int main()
{
	int n = 6;
	int a[] = {-2,11,-4,13,-5,-2};
	int besti=0,bestj=n-1; 
	int sum=MaxSum(n,a,besti,bestj);
	if(sum==-1)	sum=0;
	cout<<"最大子段和为:"<<sum<<endl;
	cout<<"初始位置:"<<besti<<"末尾位置:"<<bestj<<endl; 
	return 0;
} 

上述算法显然需要时间和空间复杂度均为 O ( n ) O(n) O(n)

  • 13
    点赞
  • 101
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值