最大子列和问题-C语言

(1)暴力枚举求解

 时间复杂度O(n^3)

int MaxSubseqSuml(int List[],int N){//在长度为N的数组List中求最大子列和的值 
	int i,j,k;//定义三个变量以便循环 
	int ThisSum,MaxSum=0;//定义两个变量分别记录当前子列和与当前最大子列和 
	for(i=0;i<N;i++)//i,j双重循环,求从i到j的子列和 
		for(j=1;j<N;j++){
			ThisSum=0;//初始化当前子列和(新的j就需要置空) 
			for(k=i;k<=j;k++)//i到j求和 
				ThisSum+=List[k];
			if(ThisSum>MaxSum)//如果子列和大于当前最大子列和 
				MaxSum=ThisSum;//则更新当前最大子列和 
		}
	return MaxSum;//返回最大子列和的值 
}

(2)改进避重求解

 时间复杂度O(n^2)

int MaxSubseqSuml(int List[],int N){//在长度为N的数组List中求最大子列和的值 
	int i,j;//定义两个变量以便循环 
	int ThisSum,MaxSum=0;//定义两个变量分别记录当前子列和与当前最大子列和 
	for(i=0;i<N;i++){//i为起点的子序列 
		ThisSum=0;	 //重置当前子列和的值 
		for(j=i;j<N;j++){//从从起点开始,终点(下标)可以是i到N-1 
			ThisSum+=List[j]; 	//当i==j时,ThisSum=List[i],其之后有i到j+1的和为i到j的和加List[j+1] 
			if(ThisSum>MaxSum)	//一重循环计算了以i为起点的所有子列和,ThisSum记录上一次的子列和,加List[j]即上一步之后为i到j即新求得子列和 
				MaxSum=ThisSum;	//每一个与最大子列和比较,若大于最大子列和,则更新 
		} 
	}
	return MaxSum;//返回最大子列和的值 
}

(3)分治法求解

 时间复杂度为O(N*log2 N)
分治法求解最大子列和就是 将原序列的最大子列和问题分解。思考最大子列可能在哪里,那么在哪里?可能在原序列左端点到中间之间,也可能是原序列中间到右端点之间,还有可能原序列经过中间点,即必定包含中间点往左端点有一段,往右端点右一段。所以我们分别求这三类情况的最大子序列,再求出最大值,就是全集(原问题)的最大值。对于左右端点到中间序列求最大子列和,与原问题属于同一类直接递归求解即可。对于经过中间点的序列,我们已经有中间点需要往两边延申试探最大和序列起点和终点,延申过程分解为从中间点向左延申和向右延申,分别求最大序列和(即从中间点分别向左向右出发,分别找起点和终点)最后两段最大合并就是经过中间点的最大子序列。

int Max3(int a,int b,int c)//返回a b c中的最大值 
{
	return a>b? a>c?a:c : b>c?b:c;
	//重点是要知道a>b?无论是否成立,执行的是一条语句 
	//原语句等价于 a>b? (a>c?a:c) :(b>c?b:c)
	//每个括号里的问号表达式里才是一条语句。所有情况如下所示 
	//a>b a>c a
	//a>b a<=c c
	//a<b b>c b
	//a<b b<=c c 
}

int DivideAndConquer(int List[],int left,int right)//求解List序列从left到right的最大子列和 
{
	//定义两个变量分别记录左端到中间的序列的最大子列和 和中间到右端的最大子列和 
	int MaxLeftSum,MaxRightSum;
	
	int MaxLeftBorderSum,MaxRightBorderSum; 
	//存放从左端点到中间点的连续(不中断)的序列的子列和的最大值 和从中间点到右端点(不中断)的序列的子列和最大值 
	
	int LeftBorderSum,RightBorderSum;//记录求左端点到中间点的连续序列子列和的值(中间存储变量),Right同理... 
	int center,i;//分别用作计算中间节点下标和循环 
	
	if(left==right){//如果序列中只有一个元素 
		if(List[left]>0)return List[left];//如果此元素为正则返回其(它就是最大子列和) 
		else return 0;//根据书本,我们规定如果最大子列和为负值则以0为最大子列和的值 
	}
	
	center=(left+right)/2;//计算中间节点下标 
	MaxLeftSum=DivideAndConquer(List,left,center);//递归求解左序列中子列最大和 
	MaxRightSum=DivideAndConquer(List,center+1,right);//递归求解右序列中子列最大和
	
	MaxLeftBorderSum=0,LeftBorderSum=0;//将需要使用的初始化为零 
	for(i=center;i>=left;i--){//求解跨越中间点向左(不中断)的最大子列和 
		LeftBorderSum+=List[i];//因为不中断所以每个值都要加,相当于不断寻找子列的起点 
		if(LeftBorderSum>MaxLeftBorderSum)//需要记录最大值,如果找到更大的则更新 
			MaxLeftBorderSum=LeftBorderSum; 
	}
	
	MaxRightBorderSum=0,RightBorderSum=0; 
	for(i=center+1;i<=right;i++){//同理求解跨越中间点+1向右(不中断)的最大子列和
		RightBorderSum+=List[i];
		if(RightBorderSum>MaxRightBorderSum)
			MaxRightBorderSum=RightBorderSum;
	}
	
	return Max3(MaxLeftSum,MaxRightSum,MaxLeftBorderSum+MaxRightBorderSum);
	//三种情况中的最大值就是最大子列和的值(跨越中间的最大子列和的值等于左边最大值加右边最大值) 
}

int MaxSubseqSuml(int List[],int N){//在长度为N的数组List中求最大子列和的值(封装函数,为了保持接口(参数列表)的一致) 
	return DivideAndConquer(List,0,N-1);//返回求得的最大子列和的值 
}

(4)在线处理

在线处理的逻辑依据是 假设有一段i到j为序列的最大子序列,则任意x>=i&&x<=j(x在最大子序列中),都一定有Sum(i,x)>=0,**即最大子序列的任意从起点出发的内部序列都应该大于等于0,否则如果小于零,那么舍去这段岂不是序列和会变大!**所以基于此开始遍历序列,遇到一个数就加入当前序列,如果序列和大于最大值则更新,如果序列和小于0则舍去序列(即将序列和清空,从下一个重新加)。这样它一定会加到最大序列(在最大序列前的序列肯定和为负被清空),然后从最大序列头开始记录序列,直到超出最大序列(没有关系,序列值已被记录,最大值被更新),于是就求出最大子序列和。

int MaxSubseqSuml(int List[],int N){//在长度为N的数组List中求最大子列和的值
	int i;//便于循环 
	int ThisSum=0,MaxSum=0;//分别记录当前序列和值和最大和 
	for(int i=0;i<N;i++){//检索整个序列 
		ThisSum+=List[i];//遇到一个数就加入序列 
		if(ThisSum>MaxSum){//如果当前序列和大于最大和 
			MaxSum=ThisSum;//则更新最大和 
		}
		else if(ThisSum<0){//如果当前和小于零(else为了少检查一遍if,因为MaxSum至少是0,大于它就不为负) 
			ThisSum=0;//如果序列为负则丢掉重新寻找序列 
		} 
	} 
	return MaxSum;//返回最大和 
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望—星空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值