最大子序列和问题

引言:


       最大连续子序列问题能够非常好的让我们了解算法对于程序设计的重要性,因此会在诸多面试笔试题目中考到,如今对它进行一个总结。


问题描写叙述:


        求数组中最大连续子序列和。

比如给定数组A={4,-3, 5,-2,-1, 2,  6,-2},则最大子序列和为11,即11=4+(-3)+5+(-2)+(-1)+2+6。


解法1——时间复杂度O(N^3)

       首先能想到的算法就是将全部的连续子序列和求出来,然后比較它们中最大的值将其返回。

求全部子序列我们能够以每一个子序列中第一个元素为循环变量。

int
MaxSubsequenceSum(const int A[], int N)
{
	int ThisSum, MaxSum, i, j, k;

	MaxSum = 0;
	for(i = 0; i < N; i++)
		for(j = i; j < N; j++)
		{
			ThisSum = 0; 
			for(k = i; k <= j; k++)	
				ThisSum += A[k];
			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}

	return MaxSum;
}

解法2——时间复杂度O(N^2)。

       解法思路跟第一种同样。都是求出全部的子序列。对其逐个比較并求出最大者。仅仅是改动了部分代码。

int
MaxSubsequenceSum(const int A[], int N)
{
	int ThisSum, MaxSum, i, j;

	MaxSum = 0;
	for(i = 0; i < N; i++){

		ThisSum = 0; 

		for(j = i; j < N; j++)
		{
			ThisSum += A[j];

			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}

	return MaxSum;
}

解法3——时间复杂度O(NlogN)。

       採用“分治算法”求解该问题。其想法是把问题分成两个大致相等的子问题。然后递归地对它们求解,这是"分"部分。

"治"阶段将两个子问题的解合并到一起请可能再做些少量的附加工作,最后得到整个问题的解。

此例中,最大子序列和可能在三处出现。或者整个出现的输入数据的左半部。或者整个出现的右半部,或者跨越输入数据的中部从而占领左右两半部分。前两种情况能够递归求解。第三种情况的最大和能够通过求出前半部分的最大和(包括前半部分的最后一个元素)以及后半部分的最大和(包括后半部分的第一个元素)而得到。

然后将这两个和加在一起。

int
Max3(int max1, int max2, int max3)
{
	int max = max1;
	if(max < max2) max = max2;
	if(max < max3) max = max3;

	return max;
}
//分治算法的实现
int 
MaxSubSum(const int A[], int Left, int Right)
{
	int MaxLeftSum, MaxRightSum;
	int MaxLeftBorderSum, MaxRightBorderSum;
	int LeftBorderSum, RightBorderSum;
	int Center, i;

	if(Left == Right)
		if(A[Left] > 0)	
			return A[Left];
		else
			return 0;

	Center = (Left + Right) / 2;
	MaxLeftSum = MaxSubSum(A, Left, Center);
	MaxRightSum = MaxSubSum(A, Center + 1, Right);

	MaxLeftBorderSum = 0;
	LeftBorderSum    = 0;
	for(i = Center; i >= Left; i--){
		LeftBorderSum += A[i];
		if(LeftBorderSum > MaxLeftBorderSum)	
			MaxLeftBorderSum = LeftBorderSum;
	}

	MaxRightBorderSum = 0;
	RightBorderSum = 0;
	for(i = Center + 1; i <= Right; i++){
		RightBorderSum += A[i];
		if(RightBorderSum > MaxRightBorderSum)
			MaxRightBorderSum = RightBorderSum;
	}

	return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
}

int 
MaxSubsequenceSum(const int A[], int N)
{
	return MaxSubSum(A, 0, N-1);
}

解法4——时间复杂度O(N)

       採用动态规划算法。

代码例如以下:在这一遍扫描数组其中,从左到右记录当前子序列的和ThisSum。若这个和不断添加,那么最大子序列的和MaxSum也不断添加(不断更新MaxSum)。假设往前扫描中遇到负数,那么当前子序列的和将会减小。此时ThisSum将会小于MaxSum,当然MaxSum也就不更新。假设ThisSum降到0时,说明前面已经扫描的那一段就能够抛弃了。这时将ThisSum置为0。然后,ThisSum将从后面開始将这个子段进行分析。若有比当前MaxSum大的子段。继续更新MaxSum

这样一趟扫描结果也就出来了。

int MaxSubsequenceSum(const int A[], int N)
{
	int ThisSum, MaxSum, j;

	ThisSum = MaxSum = 0;
	for(j = 0; j < N; j++){
		ThisSum += A[j];	

		if(ThisSum > MaxSum)
			MaxSum = ThisSum;
		else if(ThisSum < 0)
			ThisSum = 0;
	}

	return MaxSum;
}


注意:

      对于全部的元素都是负数的序列,应该考虑是不是将最大子序列设为0的问题。上面的算法中大多是这么如果的。