您的位置:首页 > 其它

《算法导论》学习笔记(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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息