算法竞赛入门经典:第八章 高效算法设计 8.1动态规划之最大连续和

/*
最大连续和:
给出一个长度为n的序列A1,A2,...,An,求最大连续和。换句话说,要求找到1<=i <= j<=n,使得Ai+Ai+1+..+Aj尽量大
b[i] = { b[i-1] + a,b[i-1] > 0
       {a,b[i-1] <0
输入:
8
1 -2 3  10 -4 7 2 -5
输出:
18(3 10 -4 7 2)
*/

/*
关键:
1 分治法3步骤:划分,递归,合并。
划分:元素二分
递归:分别求完全左半和完全右半最大连续和序列
合并:求出起点位于左半,终点位于右半的序列。先寻找最佳起点,再寻找最佳终点

本质:由于采用先划分再递归的方式,实际上是一个后递归问题
2 先求出两个子序列中的连续和较大值,再与本身最大子序列和的最大值进行比较,大者即为最大连续子序列和
3 if(high - low == 1)//递归出口
4 int mid = low + (high - low)/2;//分治第一步,划分,/取整方向是朝零,用x+(y-x)/2确保分界点靠近区间起点,技巧
5 int iMax = maxSum(iArr,low,mid) > maxSum(iArr,mid,high) ? maxSum(iArr,low,mid) : maxSum(iArr,mid,high);//分治第二步,递归,为后面比较子序列的最大值做准备
6 int iSum = 0,iL = iArr[mid-1];//我终于明白为什么从中间向两头求最大值,这样才能使两个区间的最大值进行相加,并且注意这里是从左端点的左边开展的,必须从iArr[mid-1]开始
7 	for(int i = mid -1 ; i >= 0; i--)//注意,这里易错,是从mid-1开始,因为它把mid留给了左区间,从分界点向左求最大连续子序列和
	{
		iSum += iArr[i];
		if(iSum > iL)
		{
			iL = iSum;
		}
	}
8 for(int j = mid ; j < high;j++)//取不到high要注意
9 return iMax > (iL + iR) ? iMax : (iL + iR);//分治第三步,合并
10 递归思路:设序列长度为n的次数为T(n),则T(n) =2*T(n/2) + n,T(1)=1,T(n/2)是长度为n/2的递归调用,而最后的n是合并时间,这样T(n)=O(nlogn)
11 int maxSum(int* iArr,int low,int high)//返回数组在[x,y)中国的最大连续和,继续被分为[x,mid),[mid,y)进行再处理
12 多项式时间算法:时间复杂度为多项式的算法
   指数时间算法:n!或2^n的算法
*/

#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 1024

//动态规划法
void maxSum()
{
	int n;
	while(EOF != scanf("%d",&n))
	{
		int iArr[MAXSIZE];
		for(int i = 0 ; i < n; i++)
		{
			scanf("%d",&iArr[i]);
		}
		int max = iArr[0];
		int b = 0;
		for(int i = 0 ; i < n ; i++)
		{
			if(b > 0)
			{
				b += iArr[i];
			}
			else
			{
				b = iArr[i];
			}
			if(max < b)
			{
				max = b;
			}
		}
		printf("%d\n",max);
	}
}

//方法2:Ai+Ai+1 + ...+Aj = Sj - Si-1,连续子序列之和等于两个前缀之差
void maxSum_diff()
{
	int n ;
	int iMax;
	int iCnt;
	while(EOF != scanf("%d",&n))
	{
		int iArr[MAXSIZE];
		for(int i = 0 ; i < n; i++)
		{
			scanf("%d",&iArr[i+1]);//注意。这里必须要从下标1开始赋值,因为它采用了A1,A2,...,An方式
		}
		int i;
		int iSum[MAXSIZE];
		iSum[0] = 0;
		iMax = iArr[1];
		iCnt = 0;
		for(i = 1 ; i <= n;i++)//这里有n个元素,从1开始就能取到n,注意这个与下面的循环是不相同的
		{
			//iSum[i] = iSum[i-1] + i;
			iSum[i] = iSum[i-1] + iArr[i];
		}
		for(i = 1 ; i <= n; i++)
		{
			for(int j = i ; j <= n;j++)
			{
				if(iMax < iSum[j] - iSum[i-1])//这里由于要减去i-1,因此i从1开始,以n结束 
				{
					iMax = iSum[j] - iSum[i-1];//更新最大值
				}
				iCnt++;
			}
		}
	    printf("%d %d\n",iMax,iCnt);
	}
}

//分治法
int maxSum(int* iArr,int low,int high)//返回数组在[x,y)中国的最大连续和
{
	if(high - low == 1)//递归出口
	{
		return iArr[low];
	}
	int mid = low + (high - low)/2;//分治第一步,划分
	int iMax = maxSum(iArr,low,mid) > maxSum(iArr,mid,high) ? maxSum(iArr,low,mid) : maxSum(iArr,mid,high);//分治第二步,递归,为后面比较子序列的最大值做准备
	int iSum = 0,iL = iArr[mid-1];//我终于明白为什么从中间向两头求最大值,这样才能使两个区间的最大值进行相加,并且注意这里是从左端点的左边开展的,必须从iArr[mid-1]开始
	for(int i = mid -1 ; i >= 0; i--)//注意,这里易错,是从mid-1开始,因为它把mid留给了左区间,从分界点向左求最大连续子序列和
	{
		iSum += iArr[i];
		if(iSum > iL)
		{
			iL = iSum;
		}
	}
	int iR = iArr[mid],iSum1 = 0;
	for(int j = mid ; j < high;j++)//取不到high要注意
	{
		iSum1 += iArr[j];
		if(iSum1 > iR)
		{
			iR = iSum1;
		}
	}
	return iMax > (iL + iR) ? iMax : (iL + iR);//分治第三步,合并
} 

void process()
{
	int n;
	while(EOF != scanf("%d",&n))
	{
		int iArr[MAXSIZE];
		for(int i = 0 ; i < n;i++)
		{
			scanf("%d",&iArr[i]);
		}
		int iRes = maxSum(iArr,0,n);
		printf("%d\n",iRes);
	}
}

int main(int argc,char* argv[])
{
	//maxSum();
	//maxSum_diff();
	process();
	system("pause");
	return 0;
}

你可能感兴趣的:(动态规划,最大连续和)