最大子数组问题

最大子数组求和问题:

输入:一个由整数类型构成的数组,其中每个元素可以为正数也可以是负数。

输出:该数组内所有子数组的和的最大值

例如:

数据A:A[n] = {13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7}

和的最大子数组为:{18, 20, -7, 12},其和为43

算法导论中提供了一个分治策略的方法:

使用分治的方法意味着要将子数组分解为两个规模尽可能相等的子数组。

那么最大和的子数组的存在可能有三种情况:

1,完全位于A[low...mid]中;

2,完成位于A[mid+1...high]中;

3,跨越了中点,故low<=i<=mid<=j<=high。

若是最大子数组完全位于A[low...mid]或是A[mid+1...high]中,我们仍可以递归求解,因为这两个子问题仍然是最大子数组问题,只是规模更小。因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后在这三种情况中选取最大值。

static int MaxSubSum(int A[],int Left,int Right)  
{  
    int MaxLeftSum,MaxRightSum;              
    int MaxLeftBorderSum,MaxRightBorderSum; 
    int LeftBorderSum,RightBorderSum;  
    int Center,i;  
    if(Left == Right)
        if(A[Left]>0)  
            return A[Left];  
        else  
            return 0;  
        Center=(Left+Right)/2;  
        MaxLeftSum=MaxSubSum(A,Left,Center);  
        MaxRightSum=MaxSubSum(A,Center+1,Right);  
        MaxLeftBorderSum=0;  
        LeftBorderSum=0;  
        for(i=Center;i>=Left;i--)  
        {  
            LeftBorderSum+=A[i];  
            if(LeftBorderSum>MaxLeftBorderSum)  
                MaxLeftBorderSum=LeftBorderSum;  
        }  
        MaxRightBorderSum=0;  
        RightBorderSum=0;  
        for(i=Center+1;i<=Right;i++)  
        {  
            RightBorderSum+=A[i];  
            if(RightBorderSum>MaxRightBorderSum)  
                MaxRightBorderSum=RightBorderSum;  
        }  
        int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;  
        int max2=MaxLeftBorderSum+MaxRightBorderSum;  
        return max1>max2?max1:max2;  
}  

该问题还有其它两种解决 (以下内容摘自“结构之法”博客)

一,动态规划

设sum[i]为以第i个元素结尾且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么以第i个元素结尾且和最大的连续子数组实际上,要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即sum[i] = max(sum[i-1] + a[i], a[i])。可以通过判断sum[i-1] + a[i]是否大于a[i]来做选择,而这实际上等价于判断sum[i-1]是否大于0。由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。

伪代码如下

result = a[1]
sum = a[1]

for i: 2 to LENGTH[a]
  if sum > 0
    sum += a[i]
  else
    sum = a[i]

  if sum > result
    result = sum

return result

二,扫描法

    当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新清零,不然的话这个负数将会减少接下来的和。实现:

//copyright@ July 2010/10/18  
//updated,2011.05.25.  
#include <iostream.h>  
  
int maxSum(int* a, int n)  
{  
    int sum=0;  
    //其实要处理全是负数的情况,很简单,如稍后下面第3点所见,直接把这句改成:"int sum=a[0]"即可  
    //也可以不改,当全是负数的情况,直接返回0,也不见得不行。  
    int b=0;  
      
    for(int i=0; i<n; i++)  
    {  
        if(b<0)           //...  
            b=a[i];  
        else  
            b+=a[i];  
        if(sum<b)  
            sum=b;  
    }  
    return sum;  
}  
  
int main()  
{  
    int a[10]={1, -2, 3, 10, -4, 7, 2, -5};  
    //int a[]={-1,-2,-3,-4};  //测试全是负数的用例  
    cout<<maxSum(a,8)<<endl;  
    return 0;  
}  
  
/*------------------------------------- 
解释下: 
例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5, 
那么最大的子数组为3, 10, -4, 7, 2, 
因此输出为该子数组的和18。 
 
所有的东西都在以下俩行, 
即: 
b  :  0  1  -1  3  13   9  16  18  13   
sum:  0  1   1  3  13  13  16  18  18 
   
其实算法很简单,当前面的几个数,加起来后,b<0后, 
把b重新赋值,置为下一个元素,b=a[i]。 
当b>sum,则更新sum=b; 
若b<sum,则sum保持原值,不更新。。July、10/31。

扩展:
1、如果数组是二维数组,同样要你求最大子数组的和列?
2、如果是要你求子数组的最大乘积列?
3、如果同时要求输出子段的开始和结束列?

 

第二节、Data structures and Algorithm analysis in C

下面给出《Data structures and Algorithm analysis in C》中4种实现。

//感谢网友firo  
//July、2010.06.05。  
  
//Algorithm 1:时间效率为O(n*n*n)  
int MaxSubsequenceSum1(const int A[],int N)  
{  
    int ThisSum=0 ,MaxSum=0,i,j,k;  
    for(i=0;i<N;i++)  
        for(j=i;j<N;j++)  
        {  
            ThisSum=0;  
            for(k=i;k<j;k++)  
                ThisSum+=A[k];  
              
            if(ThisSum>MaxSum)  
                MaxSum=ThisSum;  
        }  
        return MaxSum;  
}  
  
//Algorithm 2:时间效率为O(n*n)  
int MaxSubsequenceSum2(const int A[],int N)  
{  
    int ThisSum=0,MaxSum=0,i,j,k;  
    for(i=0;i<N;i++)  
    {  
        ThisSum=0;  
        for(j=i;j<N;j++)  
        {  
            ThisSum+=A[j];  
            if(ThisSum>MaxSum)  
                MaxSum=ThisSum;  
        }  
    }  
    return MaxSum;  
}  
  
//Algorithm 3:时间效率为O(n*log n)  
//算法3的主要思想:采用二分策略,将序列分成左右两份。  
//那么最长子序列有三种可能出现的情况,即  
//【1】只出现在左部分.  
//【2】只出现在右部分。  
//【3】出现在中间,同时涉及到左右两部分。  
//分情况讨论之。  
static int MaxSubSum(const int A[],int Left,int Right)  
{  
    int MaxLeftSum,MaxRightSum;              //左、右部分最大连续子序列值。对应情况【1】、【2】  
    int MaxLeftBorderSum,MaxRightBorderSum;  //从中间分别到左右两侧的最大连续子序列值,对应case【3】。  
    int LeftBorderSum,RightBorderSum;  
    int Center,i;  
    if(Left == Right)Base Case  
        if(A[Left]>0)  
            return A[Left];  
        else  
            return 0;  
        Center=(Left+Right)/2;  
        MaxLeftSum=MaxSubSum(A,Left,Center);  
        MaxRightSum=MaxSubSum(A,Center+1,Right);  
        MaxLeftBorderSum=0;  
        LeftBorderSum=0;  
        for(i=Center;i>=Left;i--)  
        {  
            LeftBorderSum+=A[i];  
            if(LeftBorderSum>MaxLeftBorderSum)  
                MaxLeftBorderSum=LeftBorderSum;  
        }  
        MaxRightBorderSum=0;  
        RightBorderSum=0;  
        for(i=Center+1;i<=Right;i++)  
        {  
            RightBorderSum+=A[i];  
            if(RightBorderSum>MaxRightBorderSum)  
                MaxRightBorderSum=RightBorderSum;  
        }  
        int max1=MaxLeftSum>MaxRightSum?MaxLeftSum:MaxRightSum;  
        int max2=MaxLeftBorderSum+MaxRightBorderSum;  
        return max1>max2?max1:max2;  
}  
  
//Algorithm 4:时间效率为O(n)  
//同上述第一节中的思路3、和4。  
int MaxSubsequenceSum(const int A[],int N)  
{  
    int ThisSum,MaxSum,j;  
    ThisSum=MaxSum=0;  
    for(j=0;j<N;j++)  
    {  
        ThisSum+=A[j];  
        if(ThisSum>MaxSum)  
            MaxSum=ThisSum;  
        else if(ThisSum<0)  
            ThisSum=0;  
    }  
    return MaxSum;  
}   






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值