《算法导论》学习笔记(2):最大子数组

分治策略

上一篇笔记中用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]中,此时 lowijmid
- A[i…j]位于右侧数组A[mid…high]中,此时 mid<ijhigh
- A[i…j]跨越了中点mid, lowimid<jhigh
解决和合并:对于前两种情况,我们可以递归的求解,因为在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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值