1. 问题描述
给定整数 A1,A2,A3,….,AN(有可能为负数),求的最大值(为了方便起见,如果所有的整数均为负数,则最大子序列和为0)
2. 分析题目
(1)为什么为了方便起见,如果所有的整数均为负数,则最大子序列和为0?
因为若都是负数,则正常来说,最大子序列的和就是其中最大的负数,这样来说很容易进行求解。在这里为了求解的方便,我们这样设定,目的是为了方便编程。具体可以见解法三。
同样,我们可以考虑其对称问题,若所有的整数都是正数,此时最大的和的子序列,就是其序列本身,也没有求解的意义。
这样我们发现,只有序列中正负相间,才有求解的必要。
3. 代码实现
解法一:暴力求解
int MaxSubsequenceSum(const int A[],int N){int ThisSum,MaxSum=0,i,j,k;for(i=0;i<N;i++){for(j=i;j<N;j++)//用i标志子序列的起点,用j标志子序列的终点{ThisSum=0;for(k=i;k<=j;j++)//k用于遍历i和j框选好的子序列{ThisSum+=A[k];}if(ThisSum>MaxSum)MaxSum=ThisSum;}}return MaxSum;}
时间复杂度为O(n^3).
解法二:对暴力求解的优化
注意到,当i固定的时候,j每增加1,k所遍历的子序列仅仅加一个元素,不需要重复计算这些子序列的值,只需要在前一个子序列的基础上加一个元素即可。
intMaxSubsequenceSum(const int A[],int N){int ThisSum,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;}
解法三:分治算法
- 算法思想:把问题分成两个大致相等的子问题,然后递归地对它们求解,这是“分”的部分。“治”阶段将两个子问题的解修补到一起并可能再做些少量的附加工作,最后得到整个问题的解。
- 具体步骤:在该问题中,如果把序列从中间分为两部分,那么最大子序列和可能在三处出现,要么整个出现在输入数据的左半部,要么整个出现在右半部,要么跨越分界线。前两种情况可以递归求解,第三种情况的最大和可以通过求出前半部分(包括前半部分的最后一个元素)的最大和以及后半部分(包含后半部分的第一个元素)的最大和而得到,此时将两个和相加。【此处是书中的原话,大概意思就是从中间往两边走,找到一个“中间序列”】
- 书中有更详细的解释,如果不理解可以参考书。
代码实现:
intMaxSum(const A[],int Left,int Right){int MaxLeftSum,MaxRightSum;int MaxLeftBorderSum,MaxRightBorderSum;int LeftBorderSum,RightBorderSum;int Center ,i;if(left==right){//此时序列只有一个值//终止条件if(arr[left]>0){//在大于0的情况下,等于其本身return arr[left];}else{return 0;//映衬题目的规则:序列不能同时为负}}Center=(Left+Right)/2;MaxLeftSum=MaxSum(A,Left,Center);MaxRightSum=MaxSum(A,Center+1,Right);//找到“中间序列”的左最大值MaxLeftBorderSum=0;//设置为0,则说明此时可能值最小为0//映衬题目的规则:序列不能同时为负//MaxRightBorderSum 同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=0;if(RightBorderSum>MaxRightBorderSum)MaxRightBorderSum=RightBorderSum;}return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);//比较函数//取三个数的最大值,没有实现,如果想要运行成功,需要实现以下}intMaxSubsequenceSum(const int A[],int N){return MaxSum(A,0,N-1);}
下面是我自己写的,变量名可能和上文不太一致,但是可以正确的运行:
#include <bits/stdc++.h>using namespace std;int maxSubSum(int arr[],int left,int right){if(left==right){if(arr[left]>0){return arr[left];}else{return 0;}}int center=(left+right)/2;int maxLeftSum=maxSubSum(arr,left,center);int maxRightSum=maxSubSum(arr,center+1,right);int borderLeftSum=0,maxBorderLeftSum=0;for(int i=center;i>=left;i--){borderLeftSum+=arr[i];if(borderLeftSum>maxBorderLeftSum){maxBorderLeftSum=borderLeftSum;}}int borderRightSum=0,maxBorderRightSum=0;for(int i=center+1;i<=right;i++){borderRightSum+=arr[i];if(borderRightSum>maxBorderRightSum){maxBorderRightSum=borderRightSum;}}int temp=max(maxLeftSum,maxRightSum);return max(temp,maxBorderRightSum+maxBorderLeftSum);}int main(){int arr[6]={-2,11,-4,13,-5,-2};printf("%d\n",maxSubSum(arr,0,5));}
我们发现,如果将递归基(也就是4-11行)改成:
if(right==left){return arr[left];}
并将 maxBorderRightSum 和 maxBorderLeftSum 的初值改成 -inf(表示负的最大值),那么我们可以处理的序列便可以是全负的,此时初值的改变会导致处理范围的改变。
2019.12.30 复习
最大子序列和问题 P17.cpp
感悟:我们要注意到,分界线是在 center和center+1之间的。
2019.12.30 时间复杂度的粗略分析
假设所用的时间为T(N),意思是关于N的一个函数,则该方法采用了分支算法,且在求“中间序列”的过程中最坏情况下扫描了整个数组,所以 T(N)=2*T(N/2)+N。递归基作为终止条件,我们可以知道T(1)=1,根据所分析的我们可以知道:
- T(2)=4=2*2
- T(4)=12=4*3
- T(8)=32=8*4
由此可见,T(N)=N*(logN+1)。
故而时间复杂度是O(NlogN)
习题 2.24
如果让 MaxLeftSum=MaxSubSum(A,Left,Center-1)
MaxRightSum=MaxSubSum(A,Center,Right)
则该算法不能正确的终止,考虑left=0,right=1,center=0,center-1=-1,无法终止运行,最主要的原因是:
MaxSubSum(A,0,1)中又包含了其本身,导致了无限循环。
解法四:最优起点
- 规律一:在求解中,我们发现最大子序列的第一个元素总是正的,因为如果“最大子序列”第一个元素是负的,那么我们总能找到一个序列,其和比“最大子序列”要大。同样,对于最大子序列,前缀不可能是和为负的子序列。
通过上述的归纳,我们便可以更快的理解此解法了。
intMaxSubsequenceSum(const int A[]){int ThisSum,MaxSum;int i;ThisSum=0;MaxSum=0;for(i=0;i<N;i++){ThisSum+=A[i];if(ThisSum>MaxSum)MaxSum=ThisSum;else if(ThisSum<0)//这个相当于“积累量”的判断//如果前面的积累量为负,则直接将 ThisSum 清零(因为保留对结果也没有贡献度)//如果前面的积累为正,则对最终结果有可能有贡献度ThisSum=0;}return MaxSum;}
复习2019.12.30 :
感悟写在上文代码中。
最大子序列求和问题 最优解法.cpp
2019.12.31 补充
特性
- 该算法只对数据进行一次扫描,当A[i]被读入并处理之后,它就不需要再被存储。
- 另外,在任意的时刻,算法都能对它已经读入的数据给出子序列问题的正确答案。
拥有这些特性的算法叫做联机算法(on-line algorithm)。
特点:需要常量的空间,以线性时间运行。
参考资料:
最大子序列和的四种算法 //此文简短的描述了原理,还附有java代码
数据结构与算法分析 学习笔记——最大子序列求和问题 //摘录了此文的代码(也是书中的代码)
