Bootstrap

算法导论-分治、最大子序列问题 转载

一.基本概念
分治法的基本步骤:
1.分解问题(Divide):把原问题分解为若干个与原问题性质相类似的子问题;
2.求解字问题(Conquer):不断分解子问题并求解;
3.合并子问题的解(Combine).


分治法的运用条件:
1.原问题可以分解为若干与原问题的解;
2.子问题可以分解并可以求解;
3.子问题的解可以合并为原问题的解;
4.分解后的子问题应互相独立,即不包含重叠子问题(如菲不那切竖列)。

求解递归函数的方法:

1.代换法
1)猜测解的行为;
2)数学归纳法证明。

2.递归树法
在递归树中,每一个结点都代表递归函数调用集合中一个子问题的代价。将树中每一层内的代价相加得到一个每层代价的集合,再将每层的代价相加得到递归是所有层次的总代价。

3.主方法

主要是记忆三种情况,根据各种情况直接写出递归函数。

T(n) = aT(n/b)+h(n)
a >=1 ; b >1 ; h(n) : 不参与递归的复杂度函数
判断n^log b (a)与h(n)的大小关系
= Θ(h(n)) :该方法的复杂度为 Θ(h(n)*lg(n))
> Θ(h(n)) :该方法的复杂度为 Θ(n^(log a/log b))
< Θ(h(n)) :该方法复杂度为 Θ(h(n))
这样可以帮助你快速的分析出你得算法的复杂度是否符合要求。


二.最大子序列问题
对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。
这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。

1.暴力解法,时间复杂度O(n~2)。
i表示子序列起始下标,j表示内部循环开始下表,遍历子序列的开头和结束下标,计算子序列的和,
int MaxSubSum (int a[], int n) { 	int i, j, maxSum = 0; 	for (i = 0; i < n; i++) 	{ 		int thisSum = 0; 		for (j = i; j < n; j++) 		{ 			thisSum += a[j]; 			if (thisSum > maxSum) 				maxSum = thisSum; 		} 	} 	return maxSum; } 



2.Kadane算法, 时间复杂度(0(n)).
原理:将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,
且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。满足条件的和最大子串,

只能是上述某个子串的前缀,而不可能跨越多个子串。

原理详细可参考:http://blog.csdn.net/joylnwang/article/details/6859677

执行流程:从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。

以array={-2, 1, -3, 4, -1, 2, 1, -5, 4}为例,来简单说明一下算法步骤。通过遍历,可以将数组分割为如下3个子串(-2),(1,-3),(4,-1,2,1,-5,4),
这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。
int KadaneMax (int a[], int n) { 	int i, j, curMax, max, curLeft, curRight; 	curMax = max = curLeft = curRight = 0; 	for (i = 0; i < n; i++) 	{ 		curMax += a[i]; 		if (curMax > 0) 		{ 			curRight = i; 			//更新最大值 			if (max < curMax) 				max = curMax; 		} 		//重新分割子串 		else 		{ 			curMax = 0; 			curLeft = curRight = i + 1; 		} 	} 	return max; }


3.递归实现,时间 复杂度为O(nlogn)。
本期的主角上场。

分治的思想:最大子序列和可能出现在三处。或者整个出现在输入数据的左半部,或者整个出现右半部,或者跨越输入数据的中部从而占据左右两个半部分。前两种情况递归求解。第三种情况的最大和可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和加在一起,求出三个值的最大值。

int recursionMax (int a[],int left, int right) { 	int i,j; 	if (left == right)		//base case 		if (a[left] > 0) 			return a[left]; 		else 			return 0;  	int center = (left + right) / 2; 	// //每次递归返回时,该值为该子段的最终左最大子序列和   	int maxLeftSum = recursionMax (a, left, center); 	//每次递归返回时,该值为该子段的右最大自序列和   	int maxRightSum = recursionMax(a, center + 1, right); 	//从中间向左扩展求子段的最大子序列和,必然包括子段的最右端数   	int maxLeftBorderSum = 0, leftBorderSum = 0;  	for (i = center; i >= left; i--) 	{ 		leftBorderSum += a[i]; 		if (leftBorderSum > maxLeftBorderSum) 			maxLeftBorderSum = leftBorderSum; 	}  	int maxRightBorderSum = 0, rightBorderSum = 0; 	for (j = center + 1; j <right; j++) 	{ 		rightBorderSum += a[j]; 		if (rightBorderSum > maxRightBorderSum) 			maxRightBorderSum = rightBorderSum; 	} 	int  tmp=maxLeftSum>maxRightSum?maxLeftSum:maxRightSum; 	if (tmp>=maxLeftBorderSum + maxRightBorderSum) return tmp; 	else return maxLeftBorderSum + maxRightBorderSum; } 

main函数调用:

int main () { 	 	int a[] = { -2, 1, -3, 4, -1, 2, 1, -5, 4,0}; 	printf ("MaxSubSum is:%6d.\n", MaxSubSum(a, 9)); 	printf ("KadaneMax is:%6d.\n", KadaneMax(a, 9)); 	printf ("recursionMax is:%6d.\n", recursionMax(a,0, 9)); 	return 0; }

运行结果:


;