赞
踩
顺序表应用7:最大子段和之分治递归法
Time Limit: 10MS Memory Limit: 400KB
Problem Description
给定n(1<=n<=50000)个整数(可能为负数)组成的序列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。 例如,当(a[1],a[2],a[3],a[4],a[5],a[6])=(-2,11,-4,13,-5,-2)时,最大子段和为20。
注意:本题目要求用分治递归法求解,除了需要输出最大子段和的值之外,还需要输出求得该结果所需的递归调用总次数。
递归调用总次数的获得,可以参考以下求菲波那切数列的代码段中全局变量count的用法:
int count=0;
int main()
{
int n,m;
int fib(int n);
scanf(“%d”,&n);
m=fib(n);
printf(“%d %d\n”,m,count);
return 0;
}
int fib(int n)
{
int s;
count++;
if((n==1)||(n==0)) return 1;
else s=fib(n-1)+fib(n-2);
return s;
}
Input
第一行输入整数n(1<=n<=50000),表示整数序列中的数据元素个数;
第二行依次输入n个整数,对应顺序表中存放的每个数据元素值。
Output
一行输出两个整数,之间以空格间隔输出:
第一个整数为所求的最大子段和;
第二个整数为用分治递归法求解最大子段和时,递归函数被调用的总次数。
Example Input
6
-2 11 -4 13 -5 -2
Example Output
20 11
Think:这道题有些难,是基本上看着神牛们的代码才敲出来的。。。所以若有雷同。。。并不是巧合(下面打的解释/注释什么的可都是我自己的东西喽。。。快夸夸伦家,^_^)
注意:最大子段和是连续的一段数(额。。好像是废话),且根据题目要求当最大值为负数时,最大连续字段和则为0(可以理解为一个数都不取)。
Q:为什么要递归求呢,JiaoGuan?
JiaoGuan:因为题目要求用递归求法,还要让你算出递归的次数呀!不信的话,我有时间再敲另一个动态规划的做法~(@^_^@)~
Q:哦,谢谢JiaoGuan,那怎么用递归求呢,JiaoGuan?
JiaoGuan:这个。。。嘛,还真是个问起。你知道二分查找了吧,还做过斐波那契等有递归的题了吧?
Q:有。。。不过这题还是不会~~(>_<)~~JiaoGuan呐~(一个比JiaoGuan还高的大汉投来楚楚可怜的眼光)
JiaoGuan:好吧,咳咳,很多递归的一个思想是把一个大问题不断拆分成一个个中问题,一个个中问题又又又又被化简为一堆堆小问题(往往可以一步解决啦)。
Q:emmm…额
JiaoGuan:比如像这题,一开的大问题是求有n个数的最大子段和,然后你可以巴啦啦写一个函数用于递归拆分求1–n/2;n/2 + 1–n;1–n三者的最大值,再写个不递归求区间left—right最大子段和(这个函数是分两路出发,从中间位置mid分别往左、往右求出,最大值a、b, 然后a + b就是区间left–right的最大。。。)的函数
以上对话来源于Q找我求教,好吧纯属。。。自己根据巨巨的代码的理解,“瞎编”(戏真多)
纯C代码如下:(多加一句,对于第一篇线性表中我留的对于当空间不够时,增加分配空间的解决方法是每输入一个就判断当前大小与已经分配好的空间大小进行比较如果大于等于size - 1就增加Add_size的空间大小,重新将地址赋值给L->data即可,而为什么我之前的代码(包括下面的)都是直接光对n进行一次总判断就完了呢?额。。。因为N的范围已经给了,没必要每次都判断一次,浪费时间,其实索性初始分配的空间就已经大于N了(这句真长),请结合代码看本句)
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int max = 50010;
int Add_Max = 100010;
typedef struct
{
int *data;
int length;
int size;
} List;
List L;
int count; //记录递归的次数
void Creat(List *L); //建立一个线性表并为其分配初始空间大小
void Add(List *L, int n); //在线性表中输入n个数
int Max(int a, int b); //求a、b中的更大的值
int Max_Sum(int left ,int right); //该函数的作用是求区间从【left---right】最大的子段和!!!很重要,建议读读看看理解函数的代码的意思(我一般吧函数声明都写在主函数上,其他函数写在主函数下面。。。
//老师说这是个好习惯,提高代码的可读性。。。行了,不吹了,自己水平依旧low,这样稍难一些的题就要参考巨巨们的代码了)
int Part_Max_Sum(int left, int right); //用于递归求最大连续子段和的重要函数!!!该函数的作用也是求区间从【left---right】最大的子段和,重点在与把区间不断缩小,递归更小的区间的最大子段和。。。
//有点二分查找里不断取中比较查找的比较的味道<-浅显
int main()
{
count = 0; //初始时,调用递归函数次数为0
Creat(&L);
int n;
scanf("%d", &n); //题目要求输入n个数
Add(&L, n); //输入
int m = Part_Max_Sum(1, n); //求最大子段和m
printf("%d %d\n", m, count); //输出最大子段和以及函数递归调用的次数
return 0;
}
void Creat(List *L)
{
L->data = (int *)malloc(max * sizeof(int));
if(!L->data) //如果分配空间失败,直接退出程序(还是有那么一点可能空间分配失败的,不是不可能,尤其是代码敲错了以及max过大等)
{
exit (-1);
}
L->size = max;
L->length = 0;
}
void Add(List *L, int n)
{
if(L->size <= n)
{
int *Add_Space;
Add_Space = (int *)realloc(L->data, (L->size + Add_Max) * sizeof(int)); //重新找更大的空间大小
if(!Add_Space)
{
exit (-1);
}
L->data = Add_Space; //把新空间地址给L->data
L->size += Add_Max; //更新当前最大的总空间大小
}
int i;
for(i = 1; i <= n; i++)
{
scanf("%d", &L->data[++L->length]);
}
}
int Max(int a, int b)
{
if(a > b)
{
return a;
}
return b;
}
int Max_Sum(int left, int right) //感觉神奇的。。。函数
{
int i, a, b, c, mid;
a = b = c = 0;
mid = (left + right) / 2;
for(i = mid; i >= left; i--) //从中间到最左,b为该段最大的子段和
{
a += L.data[i];
if(a > b)
{
b = a;
}
}
a = 0;
for(i = mid + 1; i <= right; i++) //同上
{
a += L.data[i];
if(a > c)
{
c = a;
}
}
return b + c; //返回总的 从左到右 的最大子段和
}
int Part_Max_Sum(int left, int right)
{
if(left < right)
{
count++; //递归次数 + 1
int mid, a, b, c;
mid = (left + right) / 2;
a = Part_Max_Sum(left, mid); //求。。。区间之间的最大子段和
b = Part_Max_Sum(mid + 1, right);
c = Max_Sum(left, right);
return Max(c, Max(a, b)); //返回三者最大
}
else if(left == right)
{
count++; //“相遇”了也属于递归范围
return L.data[left];
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。