码迷,mamicode.com
首页 > 其他好文 > 详细

最大子序列和(Max Subsequence Sum)问题求解

时间:2015-04-10 22:21:52      阅读:197      评论:0      收藏:0      [点我收藏+]

标签:

给定(可能有负的)整数A1A2,…,AN,求Ai,…,Aj和的最大值。

例如:输入4-35-2-126-2,最大子序列和为11(从A1A7)。

算法1:最直观的算法,穷举式地尝试所有可能。下标变量i表示子序列的开始位置,j表示结束位置,每次选定一个子序列Ai--Aj,再使用k遍历该子序列求子序列的和。代码如下:

public static int maxSubSum1(int a[]){

????????int maxSum=0;

????????

????????for(int i=0;i<a.length;i++)

????????????for(int j=i;j<a.length;j++){

????????????????int thisSum=0;

????????????????

????????????????for(int k=i;k<=j;k++)

????????????????????thisSum+=a[k];

????????????????

????????????????if(thisSum>maxSum){

????????????????????maxSum=thisSum;

????????????????}

????????????}

????????return maxSum;

}

?

复杂度分析:最外层的for循环大小为N,第二个for循环大小为N-i,假设为最坏的情况即为N,这可能会让最终的界有些大,但不影响结果。第三个循环的大小为j-i+1,我们也假设为N,因此总数为O(1·N·N·N)=O(N3)

算法2:从算法1可看出,j其实是从i递增的,因而在内部再遍历一次多余,所以可撤除最内层的for循环来避免三次的运行时间,显然改进后的算法复杂度为O(N2),代码如下:

public static int maxSubSum2(int[] a){

????????int maxSum=0;

????????for (int i = 0; i < a.length; i++) {

????????????int thisSum=0;

????????????for(int j=i;j<a.length;j++){

????????????????thisSum+=a[j];

????????????????if(thisSum>maxSum)

????????????????????maxSum=thisSum;

????????????}

????????}

????????return maxSum;????

????}

?

算法3:考虑使用分治法解决该问题。最大子序列出现的位置可能有3种情况,或者全部出现在左半部,或者全部出现在右半部,或者跨越中部从而位于左右两半部分之中。前两种情况可以递归求解,第三种情况可以通过求出前半部分(包括前半部分最后一个元素)的最大和以及后半部分(包括后半部分第一个元素)的最大和,将两者相加即可得到。

public static int maxSubSum3(int[] a){

????????return maxSumRec(a, 0, a.length-1);

????}

???? ?

????private static int maxSumRec(int[] a,int left,int right){

????????

????????if(left==right) //base class

????????????if(a[left]>0){

????????????????return a[left];

????????????}else{

????????????????return 0;

????????????}

????????int center=(left+right)/2;

????????//左边和右边最大值,递归求解

????????int maxLeftSum=maxSumRec(a, left, center);

????????int maxRightSum=maxSumRec(a, center+1, right);

????????

????????int maxLeftBorderSum=0,leftBorderSum=0;

????????//累加求左边界和

????????for(int i=center;i>=left;i--)

????????{

????????????leftBorderSum+=a[i];

????????????if(leftBorderSum>maxLeftBorderSum){

????????????????maxLeftBorderSum=leftBorderSum;

????????????}

????????}

????????

????????int maxRightBorderSum=0,rightBorderSum=0;

//累加求右边界和

????????for(int i=center+1;i<=right;i++){

????????????rightBorderSum+=a[i];

????????????if(rightBorderSum>maxRightBorderSum){

????????????????maxRightBorderSum=rightBorderSum;

????????????}

????????}

????????

????????return max3(maxLeftSum,maxRightSum,

maxLeftBorderSum+maxRightBorderSum);

????}

private static int max3(int a,int b,int c){

????????int max;

????????max=a>b?a:b;

????????max=max>c?max:c;

????????return max;

????}

?

算法分析:在遍历求maxLeftBorderSummaxRightBorderSum时,两个for循环合起来花费的时间为O(N),而在递归求maxLeftSummaxRightSum时,实质是在分别求解N/2的子序列问题(假设N是偶数),所以每个递归花费T(N/2)个时间单元,因此有递归方程:T(1)=1T(N)=2T(N/2)+O(N)。因此有,T(2)=4=2*2T(4)=12=4*3T(8)=32=8*4,以及T(16)=80=16*5,可以推出,若N=2k,有T(N)=N*(k+1)=NlogN+N=O(NlogN)。(当N不是2的幂时,分析会复杂些,但是大O的结果是不变的)

?

算法4:可以发现,负数不可能为最优子序列(和最大的子序列)的起点,因此可以推出,任何负的子序列(和为负,形如[4-5])不可能是最优子序列的前缀。如果在内循环中检测到从a[i]a[j]的子序列是负的,那么可以把它一直推进到j+1,如612-101,可以推进到-10后的1,舍弃6-10,在程序中通过将thisSum置零实现推进。该算法复杂度为O(N),很明显可以看出。

public int maxSubSum4(int[] a){

????????int maxSum=0,thisSum=0;

????????for(int i=0;i<a.length;i++){

????????????thisSum+=a[i];

????????????

????????????if(thisSum>maxSum){

????????????????maxSum=thisSum;

????????????}else if(thisSum<0){

????????????????thisSum=0;????

????????????}????????????

????????}

????????return maxSum;

????}

?

总结:算法12想法简单粗暴,复杂度较高,算法3需要理解递归分治的思想,算法4需要一定的技巧,是很多聪明算法的典型,而且算法4只需对数据进行一次扫描,一旦a[i]被读入且被处理后,就不再需要被记忆,在任意时刻,算法4能对已经读入的数据给出子序列问题的正确答案(其他算法不具有该特性),具有这种特性的算法叫做联机算法(on-line algorithm)。仅需常量空间并以线性时间运行的联机算法几乎是完美的算法。

注:本文参考自Mark Allen Weiss的Data Structures and Algorithm Analysis in Java.

最大子序列和(Max Subsequence Sum)问题求解

标签:

原文地址:http://www.cnblogs.com/damon-ly/p/4415762.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!