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

一.基本概念
分治法的基本步骤:
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;
}

运行结果:


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
分治算法是一种基于分治策略的算法,它将问题分解为若干个子问题,然后递归地解决这些子问题,最后将这些子问题的解合并起来得到原问题的解。在求解众数问题时,可以使用分治算法来解决。 众数即为出现次数最多的数。要求解一个数组中的众数,可以将数组分为两个部分,分别求出左右两个部分的众数,然后再将两个众数进行比较,选出其中的众数。 分治算法求解众数的步骤如下: 1. 将数组分为左右两个部分,分别递归求解左右两个部分的众数。 2. 如果左右两个部分的众数相同,则直接返回这个众数。 3. 如果左右两个部分的众数不同,则统计左右两个部分中各自的众数出现的次数,选出次数更多的那个数作为整个数组的众数。 Java代码实现如下: ```java public int majorityElement(int[] nums) { if (nums.length == 1) { return nums[0]; } int mid = nums.length / 2; int[] left = Arrays.copyOfRange(nums, 0, mid); int[] right = Arrays.copyOfRange(nums, mid, nums.length); int leftMajority = majorityElement(left); int rightMajority = majorityElement(right); if (leftMajority == rightMajority) { return leftMajority; } int leftCount = count(nums, leftMajority); int rightCount = count(nums, rightMajority); return leftCount > rightCount ? leftMajority : rightMajority; } private int count(int[] nums, int num) { int count = 0; for (int i = 0; i < nums.length; i++) { if (nums[i] == num) { count++; } } return count; } ``` 这样,就可以使用分治算法来解决众数问题了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值