《算法导论》学习笔记(2):最大子数组
2017-03-27 00:46
288 查看
分治策略
上一篇笔记中用C实现了归并排序,核心思想就是运用了分治策略,在分治策略中,我们递归地求解问题,每一层按照以下三个步骤:- 分解:将问题分解为更小的子问题。
- 解决:求解子问题,如果子问题足够小,则停止递归,求解。
- 合并: 将子问题的解合并成原问题。
下面以最大子数组为例。
最大子数组
问题描述
原书中有一个实际问题的背景介绍,此处略去不谈,只表述最基本的问题。在数组A[low…high]中求解一个最大的子数组A[i…j]使得从A[i]到A[j]的值加起来最大(默认为闭区间)。
思路
暴力破解的思路当然可行,尝试每个组合的可能,但时间复杂度达到了O(n2),显然不太好。现在试试分治策略的思路。分解:将数组A[low…high]分解成两个规模更小的子数组,A[low…mid]和A[mid…high],那么对于最大的子数组A[i…j]必然处于以下三种情况:
- A[i…j]位于左侧数组A[low…mid]中,此时low≤i≤j≤mid
- A[i…j]位于右侧数组A[mid…high]中,此时mid<i≤j≤high
- A[i…j]跨越了中点mid,low≤i≤mid<j≤high
解决和合并:对于前两种情况,我们可以递归的求解,因为在A[low…mid]和A[mid…high]中找出最大子数组,显然跟原问题一样,只不过是问题规模变小了而已,那么问题的解决重点在于:找到一个跨越中点的最大数组,然后在这三种情况中找出最大值。
接着思考,我们只需要找到A[i…mid]和A[mid+1…j]各自的最大值合并在一起,其结果必然是跨越中点的最大值,那么第三种情况也就迎刃而解了。跨越中点的伪代码如下:
这段程序的总共循环次数为n,有了这部分的代码,就可以设计出一个递归的求解最大子数组的算法了。
代码
按照伪代码依葫芦画瓢,为简单起见,程序只返回要求解的最大子数组和,写出的C/C++代码如下:#include <iostream> #define INF 9999999; using namespace std; //找到跨越中点的最大子数组 int findMaxCrossingSubarray(int A[], int low, int mid, int high){ //先算中点左边的 int leftSum = -INF; int sum = 0; for(int i = mid; i > low; i--){ sum = sum + A[i]; if(sum > leftSum){ leftSum = sum; } } //再算中点右边的 int rightSum = -INF; sum = 0; for(int i = mid + 1; i < high; i++){ sum = sum + A[i]; if(sum > rightSum){ rightSum = sum; } } return leftSum + rightSum; } //递归程序 int findMaxNumSubarray(int A[], int low, int high){ if(high == low) return A[low]; //递归基 else{ int mid = (low + high)/2; //找到左边最大的 int leftSum = findMaxNumSubarray(A, low, mid); //找到右边最大的 int rightSum = findMaxNumSubarray(A, mid + 1, high); //找到跨越中点最大的 int crossSum = findMaxCrossingSubarray(A, low, mid, high); //比较返回最大值 if(leftSum >=rightSum && leftSum >= crossSum) return leftSum; else if(rightSum >=leftSum && rightSum >= crossSum) return rightSum; else return crossSum; } } int main() { int A[16] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7};//测试用例 int maxSum = findMaxNumSubarray(A, 0, 15); cout << maxSum << endl; return 0; }
更新:
考虑到返回数组写起来较麻烦,因此形参增加了save[]数组,分别保存最大子数组的左边界、右边界、子数组的和(闭区间)。另外增加了输出信息,方便理清递归顺序。
#include <iostream> #define INF 9999999 using namespace std; //找到跨越中点的最大子数组 void findMaxCrossingSubarray(int A[], int save[3], int low, int mid, int high){ //先算中点左边的 int leftSum = -INF; int sum = 0; for(int i = mid; i >= low; i--){ sum = sum + A[i]; if(sum > leftSum){ leftSum = sum; save[0] = i; } } //再算中点右边的 int rightSum = -INF; sum = 0; for(int i = mid + 1; i <= high; i++){ sum = sum + A[i]; if(sum > rightSum){ rightSum = sum; save[1] = i; } } save[2] = leftSum + rightSum; } //递归程序 void findMaxNumSubarray(int A[], int save[3],int low, int high){ if(high == low) { save[0] = low; save[1] = high; save[2] = A[low]; } else{ int mid = (low + high)/2; cout <<"low: "<< low<<" mid: "<<mid<<" high: "<<high<<endl; int lsave[3] = {0, 0, -INF};//此处数组均保存的是左右边界值和最大子数组的和 int rsave[3] = {0, 0, -INF}; int csave[3] = {0, 0, -INF}; //找到左边最大的 findMaxNumSubarray(A, lsave, low, mid); int leftSum = lsave[2]; cout <<"lsave"<<lsave[0]<<" "<<lsave[1]<<" "<<lsave[2]<<endl; //找到右边最大的 findMaxNumSubarray(A, rsave, mid + 1, high); int rightSum = rsave[2]; cout <<"rsave"<<rsave[0]<<" "<<rsave[1]<<" "<<rsave[2]<<endl; //找到跨越中点最大的 findMaxCrossingSubarray(A, csave, low, mid, high); int crossSum = csave[2]; cout <<"csave"<<csave[0]<<" "<<csave[1]<<" "<<csave[2]<<endl; //比较返回最大值 if(leftSum >=rightSum && leftSum >= crossSum) { save[0] = lsave[0]; save[1] = lsave[1]; save[2] = lsave[2]; } else if(rightSum >=leftSum && rightSum >= crossSum){ save[0] = rsave[0]; save[1] = rsave[1]; save[2] = rsave[2]; } else { save[0] = csave[0]; save[1] = csave[1]; save[2] = csave[2]; } cout <<"这一轮最大者"<<save[0]<<" "<<save[1]<<" "<<save[2]<<endl; cout <<"===================================="<<endl; } } int main() { int A[16] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7}; //int A[6] = {-1, -2, 5, 6, 7, -3}; int save[3] = {0, 0, -INF}; findMaxNumSubarray(A, save, 0, 15); cout << save[0] << endl; cout << save[1] << endl; cout << save[2] << endl; return 0; }
相关文章推荐
- 《算法导论》学习笔记——最大子数组(分治策略,动态规划)
- 《算法导论》学习笔记——寻找最大子数组
- 《算法导论》学习笔记之最大子数组问题
- 算法导论第三版第四章 最大子数组和的三种解法(暴力、教材分治法、线性解法)
- 算法导论——最大子数组
- 算法导论第三版4.1最大和子数组思考
- 算法导论第三版第四章 最大子数组和的三种解法(暴力、教材分治法、线性解法)
- 算法导论第三版第四章 最大子数组和的三种解法(暴力、教材分治法、线性解法)
- 算法导论4.1DivideAndConquer寻找最大子数组
- 《编程之美》学习笔记——2.13子数组的最大乘积
- 《编程之美》学习笔记——2.10寻找数组中的最大值和最小值
- 算法导论笔记ch4_分治策略_最大连续子数组之和
- 算法导论:最大子数组问题(java实现)
- 查找数组第i小的数-《算法导论》学习笔记十
- Demo003 最大连续子数组问题(《算法导论》4.1-5)
- 算法导论程序3--最大子数组问题(Python)
- 算法导论——最大子数组问题
- 算法导论书,page41,最大子数组,java代码实现
- 算法导论之分治策略:最大子数组问题
- 算法导论——最大子数组问题