《算法导论(第三版)》笔记——分治法(Devide and Conquer)求最大连续和

以下对应英文原版第68页

本节开始提出了一个问题,关于低价购入高价卖出,分别在哪一天买卖可以获得最大利润,问题经过转化,用A[n]表示第n天的价格减去第n+1天的价格,问题转化为求最大连续和。
方法是“分治法”,把一组连续数字从中间分成两半,那么最大连续和有三种可能:
1.最大连续的和的一组“子数组”存在于“母数组”的前半段。
2.存在于后半段。
3.跨越前后半段。
请细细体会。
对于一串数组,我分别要求出它的前后半段的最大子数组,以及第三种情况的最大子数组,然后进行比较,选出最大的即为要求的结果。那么怎么求其前后半段的最大子数组呢?使用递归。下面给出代码。

先给出测试代码

#include <iostream>

using namespace std;
const int N=1000000;


struct subarray//定义了一个结构体,各参数功能如下
{
    int sum;//连续子数组的和
    int left;//子数组最左边元素的下标
    int right;//同上
};


int main()
{
    int A[]={0,13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};//从A[1]开始
    subarray a=Find_maximum_subarray(A,1,16);
    cout<<a.left<<' '<<a.right<<' '<<a.sum;

}

给出求第三种情况的代码
伪代码在原书71页

subarray Find_max_crossing_subarray(int* A,int low,int mid,int high)
{
    int left_sum=-N,right_sum=-N,max_left,max_right;
    int sum=0;
    for(int i=mid;i>=low;i--)
    {
        sum=sum+A[i];
        if(sum>left_sum)
        {
            left_sum=sum;
            max_left=i;
        }
    }
    sum=0;
    for(int j=mid+1;j<=high;j++)
    {
        sum=sum+A[j];
        if(sum>right_sum)
        {
            right_sum=sum;
            max_right=j;
        }

    }
    subarray a;
    a.sum=left_sum+right_sum;
    a.left=max_left;
    a.right=max_right;
    return a;
}

再给出分治的代码
伪代码在原书第72页

subarray Find_maximum_subarray(int *A,int low,int high)
{
    subarray a,b,c;
    if(high==low)
    {
        a.left=low;
        a.right=high;
        a.sum=A[low];
        return a;
    }
    int mid=(low+high)/2;
    a=Find_maximum_subarray(A,low,mid);
    b=Find_maximum_subarray(A,mid+1,high);
    c=Find_max_crossing_subarray(A,low,mid,high);
    if(a.sum>=b.sum&&a.sum>=c.sum)
        return a;
    else if(b.sum>=a.sum&&b.sum>=c.sum)
        return b;
    else
        return c;
}

递归过程的图类似于二叉树,理解时最好借助于图。

下面给出简化代码,把以上两个函数合二为一
参考《算法竞赛入门经典(第二版)》第223页

下面代码和以上最大的不同是范围的选用,函数传入的是左闭右开区间[x,y),代码有些改动,请细细体会。

int maxsum(int* A,int x,int y)
{
    if(y-x==1)
        return A[x];
    int m=x+(y-x)/2;
    int maxs=max(maxsum(A,x,m),maxsum(A,m,y));
    int Lsum,Rsum,v;
    Lsum=A[m-1],v=0;
    for(int i=m-1;i>=x;i--)
        Lsum=max(Lsum,v+=A[i]);
    Rsum=A[m];v=0;
    for(int i=m;i<y;i++)
        Rsum=max(Rsum,v+=A[i]);
    return max(maxs,Lsum+Rsum);
}

利用分治法计算最大连续的的时间复杂度是O(nlogn),除此之外还有一种O(n)的算法,笔者在这里依然参考了上述资料。

下面给出代码

int maxsum(int* A,int length)
{
    int S[17];
    S[0]=0;
    for(int i=1;i<=16;i++)
    {
        //S[i]表示前i项和
        S[i]=S[i-1]+A[i];
    }
    //best表示最大连续和,mins表示当前S[i]前面最小的S。
    int mins=S[1],best=S[1];
    for(int i=2;i<=length;i++)
    {
    //计算best和mins的顺序不能换,否则mins=S[i],而best=S[i]-mins,相当于一个A[i]也不选。
        best=max(best,S[i]-mins);
        mins=min(mins,S[i]);
    }
    return best;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值