问题描述给定n个整数的序列 a,a…,a. ,求序列的最大子列和 fo在这里,“子列”被定义为原始序列中连续的一段数字,我们要找的是具有最大和的一段连续的子列,并且返回它的和。如果这个最大和是负数,那么取0为最终答案。例如给定序列{-2,11,-4,13,-5,-2},其最大子列为 {11,-4,13},和为20。
方法一:分治法
第一步:将序列从中分为左、右两个序列;
第二步:递归求得两子列的最大和S左和S右;
第三步:从中分点分别向左右两边扫描,找出跨过分界线的最大子列和S中;
第四步:Smax=Max{S左,S中,S右};
//方法一:分治法
int DivideandConquer(int list[],int left,int right)
{
int MaxLeftSum, MaxRightSum;//存放左右子问题的解
int MaxLeftBorderSum, MaxRightBorderSum;//存放跨分界线的结果
int LeftBorderSum, RightBorderSum;//存放左右边的结果
int center, i;
if (left ==right )//递归终止条件,子列只剩一个元素
{
if (list[left] > 0) return list[left];
else return 0;
}
/*下边是"分"的过程*/
center = (right + left) / 2;//找到中分点
/*递归求左右子列的最大和*/
MaxLeftSum = DivideandConquer(list, left, center);
MaxRightSum = DivideandConquer(list, center + 1, right);
/*下面求跨分界线的最大子列和*/
MaxLeftBorderSum = 0;LeftBorderSum = 0;
for (i = center; i >= left; i--)/*从中线向左扫描*/
{
LeftBorderSum = LeftBorderSum + list[i];
if (LeftBorderSum > MaxLeftBorderSum)
MaxLeftBorderSum = LeftBorderSum;
}//左边扫描结束
MaxRightBorderSum = 0; RightBorderSum = 0;
for (i = center + 1; i < right; i++)/*从中线向右扫描*/
{
RightBorderSum = RightBorderSum + list[i];
if (RightBorderSum > MaxRightBorderSum)
MaxRightBorderSum = RightBorderSum;
}//右边扫描结束
/*下边返回治的结果*/
return Max(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
}
int MaxSubseqSum1(int list[], int n)
{
/*尽量使接口相同*/
return DivideandConquer(list, 0, n - 1);
}
时间复杂度:O(nlogn)
分治法的过程可能不太好理解,基本思想也就是将一个大问题转化成相同的小规模问题,每个过程的数组如下图:
方法二:在线处理
“在线”意思是指每输入一个数据就进行即时的处理,得到的结果相当于对前面已经读入的数据都成立的解。无需存储输入序列就能得到任何时刻的最大的子列和。
下面算法的核心是基于下面的一个事实:
如果整数序列{a1,a2,....,an}的最大和子列是{ai,....aj},对最大和子列中的任意序列和都大于等于0.
也很好理解,就是 如果序列和出现负的,那就相当于序列和减小,就需要将负的那个序列剔除。
//在线处理
int MaxSubsequSum2(int list[], int n)
{
int ThisSum = 0, MaxSum = 0;
int i = 0;
for (i = 0; i < n; i++)
{
ThisSum = ThisSum + list[i];//向右累加
if (ThisSum > MaxSum)
MaxSum = ThisSum;/*更新最大值*/
else if (ThisSum < 0)/*当前子列和为负*/
ThisSum = 0;/*不可能使后面部分和增大,抛弃之前的数*/
}
return MaxSum;
}
显而易见,时间复杂度为O(n),应该算是能得到的最快算法了。
过程如下:
(图片、内容来自,陈越老师主编的《数据结构》)