一.基本概念
分治法的基本步骤:
1.分解问题(Divide):把原问题分解为若干个与原问题性质相类似的子问题;
2.求解字问题(Conquer):不断分解子问题并求解;
3.合并子问题的解(Combine).
分治法的运用条件:
1.原问题可以分解为若干与原问题的解;
2.子问题可以分解并可以求解;
3.子问题的解可以合并为原问题的解;
4.分解后的子问题应互相独立,即不包含重叠子问题(如菲不那切竖列)。
求解递归函数的方法:
1.代换法
1)猜测解的行为;
2)数学归纳法证明。
2.递归树法
在递归树中,每一个结点都代表递归函数调用集合中一个子问题的代价。将树中每一层内的代价相加得到一个每层代价的集合,再将每层的代价相加得到递归是所有层次的总代价。
3.主方法
二.最大子序列问题
对于一个包含负值的数字串array[1...n],要找到他的一个子串array[i...j](0<=i<=j<=n),使得在array的所有子串中,array[i...j]的和最大。
这里我们需要注意子串和子序列之间的区别。子串是指数组中连续的若干个元素,而子序列只要求各元素的顺序与其在数组中一致,而没有连续的要求。
1.暴力解法,时间复杂度O(n~2)。
i表示子序列起始下标,j表示内部循环开始下表,遍历子序列的开头和结束下标,计算子序列的和,
2.Kadane算法, 时间复杂度(0(n)).
原理:将数组从左到右分割为若干子串,使得除了最后一个子串之外,其余子串的各元素之和小于0,
且对于所有子串array[i...j]和任意k(i<=k<j),有array[i...k]的和大于0。满足条件的和最大子串,
这里对于(-2)这样的情况,单独分为一组。各子串的最大前缀和为-2,1,6,所以目标串的最大子串和为6。
3.递归实现,时间 复杂度为O(nlogn)。
本期的主角上场。
main函数调用:
运行结果:
分治法的基本步骤:
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
执行流程:从头到尾遍历目标数组,将数组分割为满足上述条件的子串,同时得到各子串的最大前缀和,然后比较各子串的最大前缀和,得到最终答案。
这里对于(-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; }
运行结果: