从最大子段和问题看算法的优化问题

来源:互联网 发布:java图片上传原理 编辑:程序博客网 时间:2024/04/28 12:14

问题的提出:

给定n个整数(可能为负数)组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的子段和的最大值。当所给的整均为负数时定义子段和为0,依此定义,所求的最优值为

Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n

 例如,当(a1,a2,a3,a4,a4,a6)=(-2,11,-4,13,-5,-2)时,最大子段和为20

显然对于这样的算法,对于爱写程序的朋友肯定不难。。。。

让我们看看他们写出来的程序吧!^_^

Public class SubSegment{ Public static int maxSun(int[] a) {  Int sum=0;  For(int i=0;i<a.length;i++)  For(int j=I;i<a.length;j++)  {   Int thissum=0;   For(int k=0;k<j;k++) thissum+=a[k];   If(thissum>sum)   {    Sum=thissum;   }  }  Retrun sum; }}大家看得怎么样呢?果然是程序快手。。。大家看看,思路看清楚吧。在前面的两个循环是内部是计算子段(i,j)的子段和,再和当前的最大值比较,如果比当前的最大子段和大,那刚更新该值。好一个思路清晰的程序,但是它的时间复杂度怎么样呢,我们算一下,发现T(n)=O(n^3)。哇……可能大家看了程序不觉得很吓人,但这样时间复杂度也够吓人的,呵呵!
大家看得怎么样呢?果然是程序快手。。。大家看看,思路看清楚吧。在前面的两个循环是内部是计算子段(i,j)的子段和,再和当前的最大值比较,如果比当前的最大子段和大,那刚更新该值。好一个思路清晰的程序,但是它的时间复杂度怎么样呢,我们算一下,发现T(n)=O(n^3)。哇……可能大家看了程序不觉得很吓人,但这样时间复杂度也够吓人的,呵呵!
大家有没有发现上面的程序多做了重复的事情呢!认真想一下,想出来了再看下面的程序喔!

我对数组进行累加的时候,特别是顺序累的时候,我们是能够直接利用前面的累加结果的,如果s[j]表示前面j项的和,那么就有s[j+1]=s[j]+a[j+1],利用这一点,我们可以把上面的程序行进优化了。

改进后的算法如下:

Public class SubSegment

{

  Public static int maxSum(int[] a)

{

Int sum=0;

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

{

  Int thissum=0;

  For(int j=0;j<a.length;j++)

  {

   Thissum+=a[j];   //累加,本次利用上次的结果

   If(thissum>sum)

   {

sum=thissum;

}

}

}

Return sum;

}

}

哈哈,怎么样,很佩服那些能看出来的这点毛病的朋友吧。我们来看看它的时间复杂度,T(n)=O(n^2)。谢天谢地,时间复杂度终于降低了。

其实上面只是利了一个小小的技巧,把时间复杂度降低,节省了计算时间。下面我们用分治算法来处理这一问题,情况又是怎样呢?如果没有学过分治算法,那可要花多点工夫想一下拉。

最大子段和问题的分治算法

针对最大子段和这个具体问题本身的结构,我们还可以从算法设计的策略上对上述O(n^2)计算时间算法进行更进一步的改进。从问题的解结构也可以看出,它适合于用分治法求解。

如果将所给的序列a[1:n]分为长度相等的两段a[1:n/2]和a[n/2+1:n],分别求出这两段的最大子段和,则a[1:n]的最大子段和有三种情况:

(1) a[1:n]的最大子段和与a[1:n/2]的最大子段和相同

(2) a[1:n]的最大子段和与a[n/2+1:n]的最大子段和相同

(3) a[1:n]的最大子段和为a[i]+…+a[j],并且1<=i<=n/2,n/2+1<=j<=n。

对于(1)和(2)两种情况可递归求得,但是对于情况(3),容易看出a[n/2],a[n/2+1]在最大子段中。因此,我们可以在a[1:n/2]中计算出s1=max(a[n/2]+a[n/2-1]+…+a[i]),0<=i<=n/2,并在a[n/2+1:n]中计算出s2= max(a[n/2+1]+a[n/2+2]+…+a[i]),n/2+1<=i<=n。则s1+s2为出现情况(3)的最大子段和。据此可以设计出最大子段和问题的分治算法如下:

public class SubSegment

{

  private static int maxSubSum(int [] a,int left,int right)

{

int sum=0;

if(left==right) sum=a[left]>0?a[left]:0;

else

{

int center=(left+rigth)/2;

int leftsum=maxSubSum(a,left,center);

int rightsum=maxSubSum(a,center+1,right);

int s1=0;

int lefts=0;

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

{

lefts+=a[i];

if(lefts>s1)s1=lefts;

}

int s2=0;

int rights=0;

for(int i=center;i<=right;i++)

{

rights+=a[i];

if(rights>s2)s2=rights;

}

sum=s1+s2;

if(sum<leftsum) sum=leftsum;

if(sum<rightsum)sum=rightsum;

}

retrun sum;

}

public static int maxSum(int[] a)

{

return maxSubSum(a,0,a.length);

}

}

该算法所需的计算时间T(n)可满足典型的分治算法递归分式

T(n)=2T(n/2)+O(n),由此递归方程可知,T(n)=O(nlogn)。

我们简直不可相信,分治算法还有这把拿手好戏。。。感觉怎么样呢?再来看一个让我们吃惊的算法吧。好,让我们用动态规划算法来处理这个问题。

最大子段和问题的动态规划算法

在对于上述分治算法的分析中我们注意到,若记b[j]=max(a[i]+a[i+1]+..+a[j]),其中1<=i<=j,并且1<=j<=n。则所求的最大子段和为max b[j],1<=j<=n。

由b[j]的定义可易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。故b[j]的动态规划递归式为:

b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n。

据此,可设计出求最大子段和问题的动态规划算法如下:

public class SubSegment

{

public static int maxSum(int[] a)

{

int sum=0;

int b=0;

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

{

if(b>0) b+=a[i];

else b=a[i];

if(b>sum)sum=b;

}

return sum;

}

}

上述算法只用到了O(n)的时间复杂度和O(1)的空间复杂度。

我们从是最大子段和问题的优化过程中可以看出,我们把一个算法从O(n^2)优化到O(nlogn),再到O(n)是多么不容易的一件事情,所以我们在写算法的时候,尽量多想想为什么,然后根据我们的算法知识,计算一下采用某一种算法实现的时间复杂度,从而采用一种最优的算法。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/linyt/archive/2005/12/20/557552.aspx

原创粉丝点击