求最大连续子向量之和

问题引出

《编程珠玑》第8章引出一个模式识别问题:输入是具有n个浮点数的向量x,输出是输入向量的任何连续子向量中的最大和。例如:

    31 -41 59 26 -53 58 97 -93 -23 84

那么应该输出想x[2..6]的综合,即187。


分析

当所有书都是正数时,问题很容易解决,此时最大子向量就是整个输入向量。当输入向量中有负数的时候,是否应该包含某个负数并期望旁边的正数会弥补它呢?这里定义当所有输入都是负数的时候,总和最大的子向量是空向量,总和为0。


解决

1.平方算法

即对任何的正数对(i,j),计算x[i..j]的总和,求出最大总和。对于输入规模n来书,需要执行O(n2)步。

int MaxSum1(int *a,int n)
{
	int maxsofar = 0;
	int sum;
	for(int i = 0;i < n;i++)
	{
		sum = 0;
		for(int j = i;j < n;j++)
		{
			sum = sum + a[j];
			maxsofar = maxsofar>sum?maxsofar:sum;
		}
	}
	return maxsofar;
}

2.分治算法

分治原理:要解决规模为n的问题,可递归地的解决俩个规模近似为n/2的子问题,然后对它们的答案进行合并并得到整个问题的答案。

在本例中,初始问题要处理大小为n的向量,所以将它划分为两个大小近似相等的子向量a,b,然后递归地找出a,b中元素总和最大的子向量,分别为ma,mb。要注意这时候,最大子向量要么在整个a中(即ma),要么在整个b中(即mb),要么跨越a和b之间的边界。我们将跨越边界的最大子向量称为mc。

那么如何计算mc?mc在a中的部分是包含右边界的最大子向量,在b中的部分是包含左边界的最大子向量。

所以最终递归计算ma和mb,并且通过上诉方法计算mc,然后返回3个总和中的最大者。

int max(int a,int b)
{
	return a>b?a:b;
}

int MaxSum2(int *a,int l,int u)
{
	if(l == u)//一个元素
		return max(0,a[l]);
	int m = (l+u)/2;
	int lmax,rmax,sum;
	lmax = sum = 0;
	for(int i = 0;i >= l;i--)
	{
		sum = sum + a[i];
		lmax = max(lmax,sum);
	}
	rmax = sum = 0;
	for(int i = m+1;i <= u;i++)
	{
		sum = sum + a[i];
		rmax = max(rmax,sum);
	}
	return max(lmax+rmax,max(MaxSum2(a,l,m),MaxSum2(a,m+1,u)));
}
该算法实在每层递归中都执行O(n)次操作,而总计有O(logn)层递归,所以所需时间为O(nlogn)。


3.扫描算法

可以采用一种类似动态规划的算法,从数组的最左端开始扫描,一直到最右端为止,并记下所遇到的总和最大的子向量。假设我们已经解决了x[0..i-1]的问题,那么前i个元素中,最大总和子数组要么在前i-1个元素中(我们将其存储在maxsofar中),要么其结束位置为i(我们将其存储在maxendinghere中)。

int MaxSum3(int a[],int n)
{
	int maxsofar = 0;
	int maxendinghere = 0;
	for(int i = 0;i < n;i++)
	{
		maxendinghere = max(0,maxendinghere + a[i]);
		maxsofar = max(maxsofar,maxendinghere);
	}
	return maxsofar;  
}
该算法的关键在于理解maxendinghere。在循环中的第一个赋值语句之前,maxendinghere是结束位置为i-1的最大子向量的和,赋值语句将其修改为结束位置为i的最大子向量的和。若加上a[i]之后结果为正,则该赋值语句使maxendinghere增大a[i];若加上a[i]后结果为负,则该赋值语句将maxendinghere重设为0。其运行时间为O(n)。






你可能感兴趣的:(C++算法,C++学习)