顺序表应用7:最大子段和之分治递归法
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的用法:
#include <stdio.h>
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
一行输出两个整数,之间以空格间隔输出:
第一个整数为所求的最大子段和;
第二个整数为用分治递归法求解最大子段和时,递归函数被调用的总次数。
Sample Input
6
-2 11 -4 13 -5 -2
Sample Output
20 11
#include <stdio.h>
#include <stdlib.h>
int s = 0;
int a[50010];
int fenzhi(int* a, int lift, int right)
{
int mid,liftsum,rightsum,sum;
s++;
mid = (lift + right) / 2;
if (lift == right)//这是递归调用必须要有的终值情况。
{
if (a[lift] > 0)
{
return a[lift];
}
else
{
return 0;
}
}
else
{
liftsum = fenzhi(a, lift, mid);//求出左序列最大子段和
rightsum = fenzhi(a, mid + 1, right);//求出右序列最大子段和
//求跨前后两段的情况,从中间分别向两端扩展。
int ls=0, rs=0, lsum=0, rsum=0,i;
for (i =mid;i>=lift;i--)//从中间向左扩展。这里注意,中间往左的第一个必然包含在内,且注意i>=lift而不是0(右序列的左边不是0)。
{
ls = ls + a[i];
if (ls > lsum)
{
lsum = ls;
}
}
for (i = mid + 1; i <= right; i++)//从中间向右扩展。中间往右的第一个必然包含在内
{
rs = rs + a[i];
if (rs > rsum)
{
rsum = rs;
}
}
sum = lsum + rsum;//sum保存跨前后两段情况的最大子段和
if (sum < liftsum)//leftSum表示前段序列的最大子段和
{
sum = liftsum;
}
if (sum < rightsum)//rightSum表示后段序列的最大字段和
{
sum = rightsum;
}
}
return sum;
}
int main()
{
int i, n, max;
scanf("%d", &n);
for (i = 0; i < n; i++)
{
scanf("%d", &a[i]);
}
max = fenzhi(a, 0, n - 1);
printf("%d %d", max, s);
}
将所给的序列a[1:n]分为长度相等的两段子序列a[1:n/2]和a[n/2+1:n],分别求出这两段子序列的最大子段和,则总序列的最大子段和有三种情况:1)与前段相同。2)与后段相同。3)跨前后两段。
具体的分治做法是这样的:先把a[1:n]分成a[1:n/2]和a[n/2+1:n],分别求出两段子序列的最大子段和,而在求a[1:n/2]的最大子段和时,又把a[1:n/2]分成a[1:(n/2)/2]和a[(n/2)/2+1:n/2]两个子序列,照这样一直分,直至这个小序列的只有一个元素为止,求一个小序列的最大子段和无外乎是求其左边的最大子段和,求其右边的最大子段和,然后求从中间开始向两边的最大子段和,当递归到最后(即小序列中只有一个元素时),按上述三种情况求解其最大子段和并比较出三种情况中的最大者即为小子段的最大子段和,然后在从小子段开始逐步递归到大子段,最终求出答案。