最大子列和问题【简单易懂】

问题:给定N个整数的序列\left \{ A_{1},A_{2},\cdots ,A_{N} \right \},求函数f(i,j)=max\left \{ 0,\sum_{k=i}^{j} A_{k}\right \}的最大值。

算法一:

例如序列为{1,2,3,4},所以子列分别为:{1},{1,2},{1,2,3},{1,2,3,4};   {2},{2,3},{2,3,4};   {3},{3,4};   {4}。我们要做的就是依次将这些子列的和求出并比较,得出最大子列和。

首先将“1”位置作为左端的所有子列进行比较,在依次进行递加的过程中比较。

1.当前子列为{1},当前子列和(ThisSum)为1,最大子列和(MaxSum)为0,将MaxSum赋值为ThisSum;

2.当前子列为{1,2},ThisSum = 3;MaxSum = 1; 3 > 1; 赋值:MaxSum = ThisMax = 3;

3.依次进行如上操作,直到子列为{1,2,3,4},ThisSum = 10;MaxSum = 6; 10>6; 赋值:MaxSum = ThisSum = 10;

(以上3步即为i的一次循环,得出了以“1”位置作为左端的所有子列的子列和最大为10)

4.然后继续以完全相同的方法进行之后的比较(分别以“2”,“3”,“4”位置作为左端

5.最后一次的当前子列为{4},ThisSum = 4;MaxSum = 10; 4 <10;不赋值;

6.最后返回MaxSum

程序如下:

int MaxSubseqSum1(int A[],int N)
{
	int ThisSum;
	int MaxSum = 0;
	int i,j;
	
	for(i = 0; i < N; i++){	        //i是子列左端位置 
		
		ThisSum = 0;		//求一个子列和之前,将上一个子列和归零 
	
		for(j = i; j < N; j++){ //j是子列右端 
			ThisSum += A[j];	 
			ThisSum > MaxSum? MaxSum = ThisSum:MaxSum;		
		}
                    //一次i循环结束,就将“以同一个位置作为左端的所有子列”全部比较完毕 
	}
	
	return MaxSum;
}

以上算法的时间复杂度为:T(N)=O(N^{2}).

看见T(N)=O(N^{2}),我们思考是否能够将时间复杂度降到T(N)=O(N\cdot log N),这便引出了下一个算法:分而治之。

 

算法二:
采取“分而治之”的方法,通过不断地将序列“对半分”,再分别将两边的部分“对半分”。

直到不能再分,再开始返回该部分的最大值,该部分的最大值可由“左半部分最大值,右半部分最大值,跨边界最大值”比较得出。

递归函数一次所执行的任务,简单地用流程描述为:

\rightarrow分两组\rightarrow找右最大\rightarrow找左最大\rightarrow找跨边界最大\rightarrow比较得出该组最大\rightarrow返回

找右/左最大:

1.如果整个数组只有两个数,则第一个数为右最大,第二个数为左最大。

2.如果整个数组长度大于二,执行递归,变    为右半部分(左半部分)找最大的问题。

找跨边界最大:

1.先从中间开始向左加,每次加一个数,找和为最大的左子列。

2.再从中间开始向右加,每次加一个数,找和为最大的右子列。

3.将左子列和与右子列和相加,即为跨边界最大的子列和。

例如序列为{4,-3,5,-2,-1,2,6,-2},先将其从中分为两部分,然后找右半部分的最大值。发现与之前整体找最大值归属于同一类问题,再将右边分为两部分,如上步骤分下去,直到数组元素只有两个。

然后从底层开始返回,找左最大,右最大以及跨边界最大

程序如下:

int MaxSubseqSum2(int A1[],int N1)
{
	int MaxLeft = 0;//左最大 
	int MaxRight = 0;//右最大
	int MaxCross = 0;//跨界最大
	int ThisSum = 0;//求跨边界最大时的临时变量 
	int MaxCrossLeft = 0;//跨边界最大的左子列和
	int MaxCrossRight = 0;// 跨边界最大的右子列和
	int i,j;
	int A[N1 + N1%2];// 当数字个数为奇数个数时,申请N+1个空间
	int N;
	
	if(N1%2 != 0){	//当N为奇数时 
		for(i = 0; i < N1; i++)
			A[i] = A1[i];
		A[N1] = 0;	//将最后一个空间赋值为0,不影响最后结果 
		N = N1 + N1%2;
	}else{			//当N为偶数时 
		for(i = 0; i < N1; i++)
			A[i] = A1[i];
		N = N1;
	}
	
	
	//将序列对半分:1.申请两组空间 2.进行赋值 
	int ALeft[N/2],ARight[N/2]; 
	
	for(i = 0; i <= N/2;i++){
		ALeft[i] = A[i];
		ARight[i] = A[N/2 + i];
	}
		
	if(N == 2){ //当数字个数对半分,直到只剩两个数字时
		A[0] > 0? MaxLeft = A[0]:MaxLeft;  //左最大值赋值为第一个数 
		A[1] > 0? MaxRight = A[1]:MaxRight;//右最大值赋值为第二个数 
	}else{		//当数字个数仍大于2时 
		MaxLeft = MaxSubseqSum2(ALeft,N/2);//左最大值即求左半部分的最大值,与本函数研究问题一致,进行递归 
		MaxRight = MaxSubseqSum2(ARight,N/2);//右最大值,同上左最大值的处理 
		
		ThisSum = 0;
		for(i = N/2-1; i >=0; i--){
			ThisSum += A[i];
		ThisSum > MaxCrossLeft? MaxCrossLeft = ThisSum:MaxCrossLeft;//求得跨边界最大的左子列和
		}//从中间向左找最大 

		ThisSum = 0;
		for(i = N/2; i < N; i++){
			ThisSum += A[i];
		ThisSum > MaxCrossRight? MaxCrossRight = ThisSum:MaxCrossRight;//求得跨边界最大的右子列和
		}//从中间向右找最大
		MaxCross = MaxCrossLeft + MaxCrossRight;//跨界最大=左子列和+右子列和 
	}
	
	return MaxLeft > MaxRight?(MaxLeft > MaxCross?MaxLeft:MaxCross):(MaxRight > MaxCross?MaxRight:MaxCross);
                //比较并得出该部分的最大子列和 
}

以上算法的时间复杂度为:

求一个序列的最大子列和:1.求左半部分序列的最大子列和 2.求右半部分的最大子列和 3.求跨边界最大。所以可得出时间复杂度的递推公式为:T(N)=2 \cdot T(N/2)+c\cdot N,T(1)=1

递推推导为:T(N)=2 \cdot T(N/2)+c\cdot N,T(1)=1

                                 =2[2\cdot T(N/2^{2})+c\cdot N/2 ]+c\cdot N

                                 =2^{k}\cdot O(1)+c\cdot k\cdot N                其中N/2^{k}=1

                                 =O(N\cdot logN)

目前最好的算法,时间复杂度为:T(N)=O(N\cdot logN),那我们还有没有更好的算法,使时间复杂度降低到T(N)=O(N)呢?这便引出了第三个算法:在线处理

 

算法三:

就问题先分析:从左往右依次累加。

1.如果一次累加得到的数为负数,再向后累加时,该数无法使得之后的累加值更大。所以干脆直接将该负数抛弃,取当前数为0.

2.如果一次累加得到的数为正数,无论这个数是加上前一个数得到的,还是减去一个数得到的,只要它仍为正数,它仍会使得之后的数更大,所以不抛弃。

3.采取以上的策略,每输入一个数据就进行即时处理,在任何一个地方中止输入,算法都能正确给出当前的解。

程序如下:

int MaxSubseqSum3(int A[],int N)
{
	int i;
	int ThisSum = 0;
	int MaxSum = 0;
	
	for(i = 0; i < N; i++){
		ThisSum += A[i];		//向右累加 
		if(ThisSum > MaxSum){
			MaxSum = ThisSum;	//发现更大和则更新当前结果 
		}else if(ThisSum < 0){	        //如果当前子列和为负 
			ThisSum = 0;		//则不可能使后面的部分和增大,抛弃之 
		}
	}
	
	return MaxSum;
}

上述算法3的时间复杂度为T(N)=O(N),可以说是该问题能够达到的最快的算法了。

测试主函数如下:

#include <stdio.h>

#define MaxN 7

int MaxSubseqSum1(int A[],int N);
int MaxSubseqSum2(int A1[],int N1);
int MaxSubseqSum3(int A[],int N);

int main()
{
	int A[MaxN] = {4,-3,5,-2,-1,2,6};
	
	printf("MaxSum1 = %d\n",MaxSubseqSum1(A,MaxN));
	printf("MaxSum2 = %d\n",MaxSubseqSum2(A,MaxN));
	printf("MaxSum3 = %d",MaxSubseqSum3(A,MaxN));
 }

结果如下:

测试环境:win10  使用软件:DEV_C++

参考文献:陈越,《数据结构讲义》,浙江大学

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值