最大子段和之C语言
问题描述:
给定一个数组,找出其中可以构成最大数的子段,需要注意的是,这个不同于最大子序列求和
—— 最大字段求和:字段必须是连续的
—— 最大子序列求和:子序列只要是包含在原来的序列中即可
举个例子:
-1 4 -3 1 5 -1 4 -5 2
求上述的数组中的最大字段和,不难得知,最大子段和就是 10 ,也就是子段 4 -3 1 5 -1 4
思路:
首先,枚举?没有枚举解决不了的问题好吧。但是真的枚举么?在时间和空间复杂度上都会有很大的消耗;
分治?该怎么分治呢?由于在一个子段中,分治的话必须将原来的数组划分成几个部分,在本题中,大致有三种情况:
- 当最大子段和在所选的界定值左边的时候
- 当最大字段和在所选的界定值右边的时候
- 当最大子段和包含所选的界定值,也就是在界定值的两侧的时候
在第一种情况下,当最大子段和位于数组最左边的时候,通过不断地递归,保留最大的子段和,最后相加便可得到
同样,在第二种情况下,也就是说最大子段和在右边的时候,类似与上面的,通过不断地递归,相加可以求得
第三种情况相对来说比较复杂,当界定值包含在最大子段和中的时候,看上去就是类似于与问题了,但是,根据上述的想法,取得的界定值往左右两边分别寻找,相加便可以求得
在每一个划分的区间,都是采用上述三个步骤,可以得到 MAX Sum
代码演示:
#include<stdio.h>
#include<stdlib.h>
#define n 5
int a[n];
int MaxSum(int a[],int l,int r, int *sitel, int *siter)
{
int c; //中间位置
int lsum,rsum,csum; //左边、右边、中间最大和
if(l==r)
{
*sitel=l;
*siter=l;
return a[l];
}
else
{
c=(l+r)/2;
//递归求左右两边的最大字段和
lsum=MaxSum(a,l,c, sitel, siter); //左边最大字段和
//左边大时的位置临时保存
int ltemp_sitel, ltemp_siter;
ltemp_sitel=*sitel;
ltemp_siter=*siter;
rsum=MaxSum(a,c+1,r, sitel, siter); //右边最大字段和
//右边大时的位置临时保存
int rtemp_sitel, rtemp_siter;
rtemp_sitel=*sitel;
rtemp_siter=*siter;
//计算中间最大字段和
//求左半部份
int i;
int csuml=0,cleft=0;
int templ=c;
for(i=c;i>=l;i--)
{
cleft=cleft+a[i];
if(cleft>csuml){
csuml=cleft;
templ=i;
}
}
//求右半部份
int j;
int csumr=0,cright=0;
int tempr=c;
for(j=c+1;j<=r;j++)
{
cright=cright+a[j];
if(cright>csumr){
csumr=cright;
tempr=j;
}
}
//中间最大和
csum=csuml+csumr;
//位置确定
if(csum>lsum)
if(csum>rsum){ //中间最大
*sitel=templ;
*siter=tempr;
return csum;
}else{ //右边最大
*sitel=rtemp_sitel;
*siter=rtemp_siter;
return rsum;
}else if(lsum>rsum){ //左边最大
*sitel=ltemp_sitel;
*siter=ltemp_siter;
return lsum;
}
}
}
void input()
{
int i;
printf("请输入一组数字:");
for(i=0;i<n;i++)
scanf("%d",&a[i]);
}
void main()
{
int Sum;
int sitel=0;
int siter=n-1;
int i;
input();
Sum=MaxSum(a,0,n-1,&sitel, &siter);
printf("最大和为:%4d
",Sum);
printf("构成最大和的数值:");
for(i=sitel; i<=siter; i++)
printf("%4d",a[i]);
}
结果如下所示:
小结:
在分治法解决问题的时候,不断地拆分问题,将问题拆分成我们可以解决的问题即可,类似于上题,将问题最后拆分成最小的子集,可以直接判断最大子段和
递归的思想还是很重要!!!