您的位置:首页 > 其它

求连续子数组的最大和问题

2016-01-27 17:44 447 查看

http://www.cnblogs.com/allzy/p/5162815.html

前言

  这几天一直在读Weiss的数据结构书(Data
Structures and Algorithm Analysis in C:Second Edition),其中第二章是关于简单的算法分析(引入大O记号等工具),以“求连续子数组的最大和问题”为例,进行了一些说明和阐释。最大子数组和问题(原书翻译为“最大的子序列和问题”)实际上我去年夏天暑假在家刷学院OJ的时候就见过,后来秋天开算法课,在上机时也有碰到。在网上看到这还是一道经典的面试题目,在此结合Weiss的书做一点总结性讨论。

问题描述

  一个整数数组中的元素有正有负,在该数组中找出一 个连续子数组,要求该连续子数组中各元素的和最大,这个连续子数组便被称作最大连续子数组。比如数组{2,4,-7,5,2,-1,2,-4,3}的最大连续子数组为{5,2,-1,2},最大连续子数组的和为5+2-1+2=8。问题输入就是一个数组,输出该数组的“连续子数组的最大和”。

思路分析

  这个问题我所见的有四种不同时间复杂度的算法。暴力模拟显然是最慢的,而应用动态规划思想,可以得到一个O(N)级别的线性时间算法,再它的基础上稍微变形,可以得到一个更简洁的形式。Talk is cheap, let's
show codes.

代码

Solution 1: 暴力模拟,三层循环,O(n^3)级别

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;
}


Solution 2:在Solution1基础上撤出一个for循环,O(N^2)级别

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;
}


Solution 3: 分治法,分支界限的处理思路。

因为最大子序列和可能在三处出现,整个出现在数组左半部,或者整个出现在右半部,又或者跨越中间,占据左右两半部分。递归将左右子数组再分别分成两个数组,直到子数组中只含有一个元素,退出每层递归前,返回上面三种情况中的最大值。
根据这样的分析,写出代码:

static int MaxSubSum(const int A[],int Left,int Right)
{
int MaxLeftSum,MaxRightSum;              //左、右部分最大连续子序列值
int MaxLeftBorderSum,MaxRightBorderSum;  //从中间分别到左右两侧的最大连续子序列值
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;
}


另外一份写的更清晰的代码:

/*
求三个数中的最大值
*/
int Max3(int a,int b,int c)
{
int Max = a;
if(b > Max)
Max = b;
if(c > Max)
Max = c;
return Max;
}

int MaxSubSum2(int *arr,int left,int right)
{
int MaxLeftSum,MaxRightSum;    //左右边的最大和
int MaxLeftBorderSum,MaxRightBorderSum;    //含中间边界的左右部分最大和
int LeftBorderSum,RightBorderSum;    //含中间边界的左右部分当前和
int i,center;

//递归到最后的基本情况
if(left == right)
if(arr[left]>0)
return arr[left];
else
return 0;

//求含中间边界的左右部分的最大值
center = (left + right)/2;
MaxLeftBorderSum = 0;
LeftBorderSum = 0;
for(i=center;i>=left;i--)
{
LeftBorderSum += arr[i];
if(LeftBorderSum > MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}
MaxRightBorderSum = 0;
RightBorderSum = 0;
for(i=center+1;i<=right;i++)
{
RightBorderSum += arr[i];
if(RightBorderSum > MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
}

//递归求左右部分最大值
MaxLeftSum = MaxSubSum2(arr,left,center);
MaxRightSum = MaxSubSum2(arr,center+1,right);

//返回三者中的最大值
return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
}

/*
将分支策略实现的算法封装起来
*/
int MaxSubSum2_1(int *arr,int len)
{
return MaxSubSum2(arr,0,len-1);
}


以上代码时间复杂度为O(NlogN)

Solution 4: 动态规划(DP)

不难得出,针对这个问题,递推公式是DP[i] = max{DP[i-1] + A[i],A[i]};既然转移方程出来了,意味着写一层循环就可以解决这个问题。
将这个转移方程变为形象的if-else判断,代码(来源于Weiss的书)为:

int MaxSubSum(int arr[],int len)
{
int i;
int MaxSum = 0;
int ThisSum= 0;
for(i=0;i<len;i++)
{
ThisSum+= arr[i];
if(ThisSum > MaxSum)
MaxSum = ThisSum;
/*如果累加和出现小于0的情况,
则和最大的子序列肯定不可能包含前面的元素,
这时将累加和置0,从下个元素重新开始累加  */
else if(ThisSum< 0)
ThisSum= 0;
}
return MaxSum;
}


最后贴一份我去年暑假过学校OJ题时AC的代码。

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int MaxsumUlt(int * arr, int size)
{
int maxSum = 0xf0000000;
int sum = 0;
for(int i = 0; i < size; ++i)
{
if(sum < 0)
{
sum = arr[i];
}
else
{
sum += arr[i];
}
if(sum > maxSum)
{
maxSum = sum;
}
}
return maxSum;
}

int main(){
int n;
while(cin>>n){
int a
;
for(int i = 0;i<n;i++)
cin>>a[i];
printf("%d \n",MaxsumUlt(a,n));
}

}


其他

  Weiss的这本数据结构书的确不错,跟我大一下学校开数据结构课程所用的教材(两本清华出版的)简直云泥之别。只不过对于没有算法基础的初学者而言,可能会有一定难度,篇幅简略必然带来理解难度的增加。

参考资料

  http://blog.csdn.net/ns_code/article/details/20942045
  http://blog.sina.com.cn/s/blog_60d6fadc0101369g.html
  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: