1.减治法求最大子序和

给定n个整数(可能有负)的序列,求最大子段和

例如序列(-20,11,-4,13,-5,-2)最大子段和为20(子段为{11,-4,13})

 

思路:

这道题目的难点在于子段起点和终点不确定:想想吧,如果题目规定必须包含a[0],那还不好求吗?

只要设置两个变量a,b,a负责记录前最大值,b负责从a[0]累加到a[n],如果b>a,则令a=b,只需扫描一趟就能求解出来。

 

所以可以想到,在子段任意固定一点,以此为起点向序列左右两端探索最大值,然后将这些值加起来不就行了吗?

当然出于效率的考虑,定点选取的肯定是序列中心



                                        
序列    a0    a1    a2    a3    ...    aj    ...    an-2    an-1    an
                                       ↑
            ←--  向左探索(最大值s1)  定点 向右探索(最大值s2)--→
其中j=(0+n)/2

最大序列和=s1+s2

求s1:

int s1,left;

for(int i=j;i>=0;--i){

    left+=a[i];

    if(left>s1) left=s1;

}

求s2:

int s2,right;

for(int i=j+1;i<=right;++i){
    
    right+=a[i];

    if(right>s2) right=s2;

}

 

但是也有一个很明显的问题:如果最大子序在a[j]的右边或左边呢?

比如最大子序在a[j]左边,即a[0]~a[j-1]。但是按照上面的算法,a[j]无论如何都被加进去了,所以肯定是算不对的,那怎么办呢?

没办法,我们得把左(a[0]~a[j-1])的最大子序和:leftMax和右(a[j+1]~a[n])的最大子序和:rightMax给算出来,然后比较leftMax,rightMax,(s1+s2),看看哪个大。

怎么算leftMax以及rightMax呢?分而自治:

我们将a[0]~a[j-1]再次分为左中右三块,对其左中右再分为左中右三块。。。往复下去,这些序列迟早变成长度为1的单个序列,对于这些序列,它们的最大子序列很明显就是它们自己啦

例: 求序列 -20,11,-4,13,-5,-2的最大子序和 Max

          ←--s1       |        s2--→
序列    -20    11    -4    13    -5    -2

1、由上面的分析,很容易算出 S1+S2=20    

由于a[0]~a[n/2](左) 或 a[n/2+1]~a[n](右) 可能会出现最大值,所以将序列划分为

{-20,11}(左)和{13,-5,-2}(右),并分别求其最大子序和 MaxLef 及 MaxRig



2、对于{-20,11},由于只有两个元素,所以很明显:

max(左子序和)=-20,max(右子序和)=11,s1+s2=11;

∴MaxLef = max( max(左子序和) , s1+s2 , max(右子序和) ) = max(-20,11,11)=11



3、对于{13,-5,-2}:

s1+s2=8    由于其左右子序列均只有一个元素,

所以max(左子序和)=左子序列=13,max(右子序和)=右子序列=-2

∴MaxRig = max( max(左子序和) , s1+s2 , max(右子序和) ) = max(13,8,-2)=13



4、求得了S1+S2,MaxLef,MaxRig,

那么对于序列 -20,11,-4,13,-5,-2的最大子序和很自然能求出来:

Max = max ( MaxLef,S1+S2,MaxRig ) = max(11,20,13) = 20




 

代码:

#include<stdio.h>

#include<stdlib.h>

int MaxSum(int a[],int left,int right){

	int sum=0,midSum=0,leftSum=0,rightSum=0;

	int center,s1,s2,lefts,rights;
	
	if(left==right) {sum=a[left];}

	else{

		center=(left+right)/2;

		leftSum=MaxSum(a,left,center);

		rightSum=MaxSum(a,center+1,right);

		s1=0,lefts=0;

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

			lefts+=a[i];

			if(lefts>s1) s1=lefts;
		
		}

		s2=0;rights=0;

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

			rights+=a[j];

			if(rights>s2) s2=rights;

		}

		midSum=s1+s2;

		if(midSum<leftSum) sum=leftSum;

		else sum=midSum;

		if(sum<rightSum) sum=rightSum;

		printf("\n\n序列:");
		for(int k=left;k<=right;++k){
			printf("%d ",a[k]);
		}
		printf("\n");

		printf("左子序列:");
		for(int i2=left;i2<=center;++i2){
			printf("%d ",a[i2]);
		}
		printf(" 最大值%d\n",leftSum);

		printf("右子序列:");
		for(int i3=center+1;i3<=right;++i3){
			printf("%d ",a[i3]);
		}
		printf(" 最大值%d\n",rightSum);

		printf("中序列:");
		for(int i4=left;i4<=right;++i4){
			printf("%d ",a[i4]);
		}
		printf("s1=%d,s2=%d\n",s1,s2);

	}

	return sum;

}

int main(){

	int a[6]={-20,11,-4,13,-5,-2};

	printf("全序列:\n");

	for(int i=0;i<5;++i){
		printf("%d ",a[i]);
	}

	printf("\n");

	printf("\n最大值:%d\n",MaxSum(a,0,5));

	return 0;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值