转载自博客园不思蜀的转载文章:最大子序列和算法分析

Maximum Continuous Subsequence Sum

最大连续子序列求和详解

1.        问题描述

输入一个整数序列(浮点数序列也适合本处讲的算法),求出其中连续子序列求和的最大值。

2.        算法分析

2.1.        算法一

2.1.1.       算法描述

遍历所有子序列并求和,比较得出其中的最大值。

2.1.2.       代码描述

public static int maxSubSumCubic(int[] array) {
                int maxSum = 0;  //最大子序列求和
                //start表示要求和的子序列的开始索引,end表示结束索引
                for(int start = 0; start < array.length; start++) {
                       for(int end = start; end < array.length; end++) {
                              int thisSum = 0;   //当前子序列求和
                              //求出array[start]~array[end]子序列的和
                              for(int index = start; index <= end; index++) {
                                     thisSum += array[index];
                          }
                          //判断是否大于之前得到的最大子序列求和
                          if(thisSum > maxSum) {
                                 maxSum = thisSum;
                          }
                   }
            }
            return maxSum;
     }

2.1.3.       算法分析

设输入序列长度为N,算法一有三个循环嵌套,第4行的循环长度为N。第5行的循环长度为N-start+1,因为我们考虑的是最差性能,所以可取为最大的N。第8行的循环长度是end-start+1,同理可取为N。所以可得算法的运行时间是O(N*N*N)=O(N^3 ),即算法的运行时间是以输入长度的立方增长的。可想而知,一旦输入长度变大,算法的运行效率将慢得无法接受,这也从反面说明了算法设计的重要性。

2.2.        算法二

2.2.1.       算法描述

算法设计的一个重要原则就是“不要重复做事”。在算法一中,对array[start]~array[end]子序列求和,可以由上一次求和array[start]~array[end-1]的结果加上array[end]得到,而不用从头开始计算。

2.2.2.       代码描述
public static int maxSubSumQuadratic(int[] array) {
                int maxSum = 0;  //最大子序列求和
                //start表示要求和的子序列的开始索引,end表示结束索引
                for(int start = 0; start < array.length; start++) {
                       int thisSum = 0;   //当前子序列求和
                       for(int end = start; end < array.length; end++) {                            
                  //已求得的array[start]~array[end-1]子序列的和加上array[end]
                              //得到array[start]~array[end]子序列的和
                              thisSum += array[end];                         
                          //判断是否大于之前得到的最大子序列求和
                          if(thisSum > maxSum) {
                                 maxSum = thisSum;
                          }
                   }
            }
            return maxSum;
     }
2.2.3.       算法分析

算法二比算法一少了一个循环,同之前的分析一样,容易得到该算法的运行时间为O(N^2),算法的运行时间是以输入长度的立方增长的。

2.3.        算法三

2.3.1.       算法描述

考虑把输入序列从中间分成两半,那么最大和子序列的位置存在三种情况:1、最大和子序列完全在输入序列的左半部分;2、最大和子序列完全在输入序列的右半部分;3、最大和子序列跨越左右两部分。

所以,为了得到输入序列的最大子序列和,我们可以分别求出左半部分的最大子序列和、右半部分的最大子序列和、以及跨越左右两部分的最大子序列和,比较三者得出最大者就是要求的。

求左半部分的最大子序列和,可把左半部分作为新的输入序列通过该算法递归求出。右半部分的最大子序列和也同理。

接下来就是求解跨越左右两部分的最大子序列和,也就是求出左半部分中包含最右边元素(如图中的12)的子序列的最大和,和右半部分中包含最左边(如图中的6)的子序列的最大和,将两者相加即为跨越左右两个部分的最大子序列和。

另外还有一个需要说明的就是,对于有奇数个元素的数组,那么左右两半部分并不是平分的,但这其实不是问题。上面的算法并不要求是两半部分,分成任意两部分都可以。

2.3.2.       代码描述
public static int maxSubSumRec(int[] array, int left, int right) {
                //递归的基准情况:待处理序列只有一个元素
                if(left == right) {
                       //空集也算是子序列,空集和为0,所以最大子序列和最小为0
                       if(array[left] > 0)
                              return array[left];
                       else
                              return 0;
                }
           
            //递归求出左半部分和右半部分的最大子序列和
            int center = (left + right) / 2;
            int maxLeftSum = maxSubSumRec(array, left, center);
            int maxRightSum = maxSubSumRec(array, center + 1, right);
           
            //求出左半部分中包含最右边元素的子序列的最大和             
            int maxLeftBorderSum = 0, leftBorderSum = 0;
            for(int i = center; i >= left; i--) {
                   leftBorderSum += array[i];
                   if(leftBorderSum > maxLeftBorderSum) {
                          maxLeftBorderSum = leftBorderSum;
                   }
            }
           
            //求出右半部分中包含最左边元素的子序列的最大和             
            int maxRightBorderSum = 0, rightBorderSum = 0;
            for(int i = center + 1; i <= right; i++) {
                   rightBorderSum += array[i];
                   if(rightBorderSum > maxRightBorderSum) {
                          maxRightBorderSum = rightBorderSum;
                   }
            }
           
            //跨越两个部分的最大子序列和
            int maxLeftRightSum = maxLeftBorderSum + maxRightBorderSum;
           
            //maxLeftSum、maxRightSum、maxLeftRightSum中的最大值即为最大子序列和
            int maxSubSum = 0;
            maxSubSum = maxLeftSum > maxRightSum ? maxLeftSum: maxRightSum;
            maxSubSum = maxSubSum > maxLeftRightSum ? maxSubSum: maxLeftRightSum;
           
            return maxSubSum;
     }
2.3.3.       算法分析

设T(N)表示输入序列长度为N时的运行时间,若N=1,即只有一个元素,那么left==right,所以有T(1)=1。对于N>1,程序需运行两个递归调用和两个for循环。其中每个递归调用的运行时间相当于输入长度为N/2时算法的运行时间T(N/2),两个递归则为2T(N/2)。每个for循环的运行次数为N/2,循环体中的语句运行时间是常数,所以两个for循环的运行时间为O(N/2*2)=O(N)。所以对于N>1,有T(N)=2T(N/2)+O(N),为了方便,可用N代替O(N),数量级不变,所以T(N)=2T(N/2)+N。在Weiss的《数据结构与算法分析Java语言描述(第二版)》中,作者通过观察的方式得出T(N)=N*(K+1),其中N=2^k ,所以有T(N)=N*(k+1)=NlogN +N=O(N )。

这里笔者自己尝试推导出这个结果,如有疏误或更好的方法,请不吝指教。同样,假设N为2的K次方,如果不是2的K次方,直接推导可能会比较复杂。当然我们可以这么理解,如果N不是2的K次方,可以通过在输入序列开头加入n个0,使N=N+n变成2的K次方。因为增加了输入的长度,所以运行时间比原本的要长,因为算法分析是最差时的性能,所以能用变长了的运行时间来代替原来的运行时间。所以问题同样转化成N为2的K次方时,T(N)的表示式怎么求。好了,废话不多说,直接推导。

由T(N)=2T(N/2)+N两边同除以N,得

(T(N))/N=(T(N/2))/(N/2)+1   =>  (T(N))/N-(T(N/2))/(N/2)=1

令F(N)=(T(N))/N ,则有

F(N)-F(N/2)=1

往下递推有

F(N/2)-F(N/4)=1

    ...

    F(2) - F(1)=1

以上K(=logN )个式子相加可得

F(N)-F(1)=1+...+1=logN

T(N)/N-T(1)/1=logN

可得

T(N)=N logN+N=O(N logN)

由以上推理过程可得该算法的运行时间为

T(N)=O(N logN)

2.4.        算法四

2.4.1.       算法描述

设输入序列为A,长度为N,从a0开始求和并记录最大值,如a(0),a(0)+a(1),a(0)+a(1)+a(2)…,直到开始出现求和小于0则停止。设加到a(i)时开始小于0,即有a(0),a(0)+a(1),…,a(0)+…+a(p-1)都大于0,而a(0)+a(1)+…+a(p)<0。此时,可从a(p+1)重新开始求和并记录最大值。为什么可以这么做呢?我们把从a(1)到a(p)之间开始的子序列分为两种情形(设子序列的开始索引为start,结束索引为end):

1、end<=p,a(start)+…+a(end)=a(0)+…+a(start)+…a(end) –[a(0)+…+a(start-1)]。由前面知,对于start-1<p,有a(0)+…+a(start-1)>0,所以可得到a(0)+…+a(start)+…a(end)> a(start)+…+a(end)。又由于a(0)+…+a(start)+…a(end)已经考虑过了,所以比其小的子序列无需考虑。

2、end>p,因为1<=start<=p,有a(0)+…+a(start-1)>0而a(0)+…+a(start)+…a(p)<0,所以有a(start)+…a(p)= a(0)+…+a(start)+…a(p)-[ a(0)+…+a(start-1)]<0。对于end>p,有a(start)+…+a(p)+…+a(end)<a(p+1)+…a(end)。

综上所述,只需要从a(p+1)开始重新求和,重复以上步骤即可得到最大子序列求和。

2.4.2.       代码描述
public static int maxSubSumLinear(int[] array) {
                int maxSum = 0, thisSum = 0;
                for(int j = 0; j < array.length; j++) {
                       thisSum += array[j];
                       if (thisSum < 0) {
                              thisSum = 0;
                       }
                       else if(thisSum > maxSum) {
                              maxSum = thisSum;
                   }
            }
            return maxSum;
     }
2.4.3.       算法分析

算法四只有一个for循环,且循环里面语句的执行时间为常量,所以T(N)=O(N)。可见该算法的运行时间是线性增长的,比前三个算法的性能要好。另外一个优点就在于该算法每次就需要使用一个输入值array[j],所以如果是在硬盘或者网络传输的环境下,我们可以实现串行读取,减少内存占用。

转自:

https://www.cnblogs.com/blackiesong/p/6076389.html

http://blog.csdn.net/superchanon/article/details/8228212

References:

《数据结构与算法分析Java语言描述(第2版),Mark Allen Weiss







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值