【算法导论】最大子数组问题

       在算法导论中,最大子数组问题是在股票买卖的背景下提出来的。显然,我们都希望可以在“低位买进,高位卖出”,这样获利是最多的。不过,其实这算法对于实际操作也没多大意义,因为,你根本不知道目前的价钱是高位,还是低位。或许可以用ML的方法去学习出未来一天股价大涨还是大跌的特征,就可以预测未来,实际上应该也有人尝试过。不过估计是学不到很好的特征的,毕竟影响因素太多了。

       例如下图放映了某只股票在17天内的股价变化,如果你可以在第7天之后买进,第11天卖出,那么收益是很可观的,可以达到43,在这17天内没有更好的组合来使得利益最大化了。

       要怎么求解这个问题呢?最直接的方法也是最暴力的方法--排列组合,把所有情况都拿去算一遍,然后把好的留下来。当然,问题是可以解决的,但是,复杂度太高了,是o(n^2)。换个角度,把每天的股价换成和前一天的差来组织数据,那么这么多天的数据其实就构成一个数组A,我们要的是找出这个数组A的一个子数组,这个子数组的和最大,这问题就叫做最大子数组(maximum subarray)问题。


       本文介绍一种高效的求解方法,来找到一个最大子数组。为什么强调“一个最大子数组”,因为有时候答案并不唯一。例如,股价连续几天一样,那么这些天里哪天买进都不影响结果。我们使用分治策略来求解这个问题。问什么要用分治算法,其实也就是为了降低复杂度。要在一个大数组找子数组,可以考虑两个长度更小的子数组中分别找到最大子数组,假如可以这两个子数组是可以连起来的,那么合起来就是答案了。下面理解一下下面的结论:

如果把数组A分成两个子数组A1和A2,A1=A[low,mid],A2=A[mid+1,high],其中low和high分别是A的最大和最小下标,mid=(low+high)/2,向上向下取整都一样,那么A[low,high]的任何子数组A[i,j]必然是以下三种情况之一:

(1)完全位于子数组A[low,mid]中,low<=i<=j<=mid;

(2)完全位于子数组A[mid+1,high]中,mid+1<=i<=j<=high;

(3)跨越了中点,因此low<=i<mid<=j<=high

       容易看到,如果把两个子数组继续分下去,就可以分成4个,然后是8个,一直分下去,直到low=i=j=high就不可可以再分了。把大问题分解成小问题求解,然后再把小问题的解合并起来,就是分治策略的思想。本问题和其他分治问题不同的地方在于,不是简单地把大问题当成小规模问题的示例,合并的条件是必须跨越中点。

       下面给出上面问题求解的c源代码,关键的地方是一下几句代码:

        threeParmLeft=findMaxinumSubarray(number,low,mid);
        threeParmRight=findMaxinumSubarray(number,mid+1,high);
        threeParmCross=findMaxCrossingSubarray(number,low,mid,high);



#include <stdio.h>
#define LENGTH 16
//定义一个结构,记录了左右下表和总和,原因在于用于数据返还
typedef struct
   {
     int low;
     int high;
     int sum;
   }parm;

parm findMaxCrossingSubarray(int number[],int low,int mid,int high);
parm findMaxinumSubarray(int number[],int low,int high);

int main()
{
    int number[LENGTH] = {13,-3,-25,20,-3,-16,-23,18,20,-7,12,-5,-22,15,-4,7};

    parm threeParm;
    threeParm=findMaxinumSubarray(number,0,LENGTH-1);
    printf("%d,%d,%d",threeParm.low,threeParm.high,threeParm.sum);
    return 0;

}
parm findMaxinumSubarray(int number[],int low,int high)
{
    int mid;
    parm threeParmLeft;
    parm threeParmRight;
    parm threeParmCross;
    parm threeParm;
//递归停止条件
    if (low==high)
    {
        threeParm.low=low;
        threeParm.high=high;
        threeParm.sum=number[low];
        return threeParm;
    }
    else
    {
        //分治策略        
        mid=(low+high)/2;

        threeParmLeft=findMaxinumSubarray(number,low,mid);
        threeParmRight=findMaxinumSubarray(number,mid+1,high);
        threeParmCross=findMaxCrossingSubarray(number,low,mid,high);
        //下标更新        
        if (threeParmLeft.sum>threeParmRight.sum&&threeParmLeft.sum>threeParmCross.sum)
            return threeParmLeft;
        else if (threeParmRight.sum>threeParmLeft.sum&&threeParmRight.sum>threeParmCross.sum)
              return threeParmRight;
        else
            return threeParmCross;
      }
}
parm findMaxCrossingSubarray(int number[],int low,int mid,int high)
{
    int i;
    parm threeParm;
    //左搜索
    int sum=0;
    int leftSum=number[mid]-1;
    int left;

    for (i=mid;i>=low;i--)
    {
        sum=sum+number[i];
        if(sum>leftSum)
        {
            leftSum=sum;
            left=i;
        }
    }
    //右搜索
    sum=0;
    int rightSum=number[mid+1]-1;
    int right;
    for (i=mid+1;i<=high;i++)
    {
        sum=sum+number[i];
        if(sum>rightSum)
        {
            rightSum=sum;
            right=i;
        }
    }
    threeParm.low=left;
    threeParm.high=right;
    threeParm.sum=leftSum+rightSum;
    return threeParm;
}


  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值