Max{0,a[i]+a[i+1]+…+a[j]},1<=i<=j<=n
例如,当(a1,a2,a3,a4,a4,a6)=(-2,11,-4,13,-5,-2)时,最大子段和为20。
简单算法:
/*
** 简单算法
** 两个for循环,需要O(n^2)的时间
** 空间就存储一个数组,但不是最优
*/
#include<stdio.h>
// besti,bestj记录最大段和时的头尾
// 元素存储在a数组中,a[0]...a[n-1]
int MaxSum(int n, int a[], int &besti, int &bestj) {
int i , j , k;
int sum = 0 , thissum;
// 分别计算以每个元素为起始位置求解最大值
for(i=0 ; i<n ; i++) {
thissum = 0;
// 计算以a[i]为起始元素
// a[k]为结尾,k从i...n-1,记录下最大值
for(j=i ; j<n ; j++) {
thissum += a[j];
// 当前值如果大于已求最大值,则存储
if(thissum > sum) {
sum = thissum;
besti = i;
bestj = j;
}
}
}
return sum;
}
int main() {
int n , a[100];
int i , besti , bestj;
int sum;
printf("请输入元素的个数:\n");
scanf("%d",&n);
printf("请输入各个元素:\n");
for(i=0 ; i<n ; i++) {
scanf("%d" , &a[i]);
}
sum = MaxSum(n , a , besti , bestj);
printf("最大字段和为:\n");
for(i=besti ; i<bestj ; i++) {
printf("%d + " , a[i]);
}
printf("%d = %d\n" , a[bestj] , sum);
return 0;
}
如果将给定的序列a[1...n]分成两段长度相近的子段,分别为a[1...n/2] 和 a[n/2+1 ... n] , 试分别求出这两个子段的最大子段和,
则a[1... n]的最大子段和的情形有如下三种:
(1)a[1...n]的最大子段和与a[1...n/2]的最大子段和相同,也就是最大子段和在左半边
(2)a[1...n]的最大子段和与a[n/2+1...n]的最大子段和相同,也就是最大子段和在右半边
(3) a[1...n]的最大子段和跨越两边,和为a[i] + a[i+1] + ... + a[j] , 期中 1<= i <= n/2 , n/2+1<= j <= n
其中(1)、(2)两种情形可以用递归求得,而对于第(3)中则可以通过分别计算s1 , s2
而 s1 为从中间位置a[n/2] 往前加求得最大值
而 s2为从中间位置a[n/2+1]往后加求的最大值
然后 s1 + s2 , 然后比较三种情形下的最大值,求出最大的即为最大子段和
#include<stdio.h>
/*
** 使用分治法
** 时间为O(nlogn)
*/
int MaxSumtwo(int left, int right, int a[]) {
int sum = 0 , i , j;
int center , leftsum , rightsum;
int s1 , s2;
int lefts , rights;
// 递归出口
if(left == right) {
sum = a[left]>0 ? a[left]:0;
} else {
center = (left + right) / 2;
// 前半段
leftsum = MaxSumtwo(left,center,a);
// 后半段
rightsum = MaxSumtwo(center+1,right,a);
// 分别计算s1 和 s2
s1 = 0 ; lefts = 0;
for(i=center ; i>=left ; i--) {
lefts += a[i];
if(lefts > s1) {
s1 = lefts;
}
}
s2 = 0 ; rights = 0;
for(j=center+1 ; j<=right ; j++) {
rights += a[j];
if(rights >= s2) {
s2 = rights;
}
}
// 三种情况下的sum值进行比较
sum = s1 + s2;
if(leftsum > sum) {
sum = leftsum;
}
if(rightsum > sum) {
sum = rightsum;
}
}
return sum;
}
int main() {
int n , a[100];
int i;
int sum;
printf("请输入元素的个数:\n");
scanf("%d",&n);
printf("请输入各个元素:\n");
for(i=0 ; i<n ; i++) {
scanf("%d" , &a[i]);
}
sum = MaxSumtwo(0 , n-1 , a);
printf("分治法求得的最大子段和为:%d\n" , sum);
return 0;
}
动态规划算法:
找出数组的最大子段和。(即 max (a[i]+a[i+1]+...+a[j]) 0<=i<=j<=n-1)
现在我们的思路是: 记s[i]为 a[0]到a[i]中包含a[i] (即以a[i]为结尾)的最大子段和,则s[i]如何用s[i-1]来表示?
s[i] = s[i-1] + a[i]; s[i-1]>=0 或者
s[i] = a[i]; s[i-1]<0
所以根据s[i-1]来计算s[i]时会遇到这两种情况。计算s[i]时只用到s[i-1]和a[i], 所以我们可以只保留s[i-1]的值,对于s[i-2]及以前的值可以不保存。
在s[i]中找最大的,就是我们的结果了。
那究竟哪一段是最大的呢。这个方法不唯一。比如,你可以用数组记录每一个s[i],那如果s[i] = a[i]的时候,说明a[i-1]不在s[i]的计算结果中,a[i-1]就没被选择。
现在我们用一个辅助数组P来记录一下, p[i] = 1 表示 在计算s[i]时 包含了 p[i-1]; p[i] = 0 表示 p[i-1]没有被包含。
#include<stdio.h>
/*
** 使用动态规划
** 时间为O(n)
*/
int MaxSumthree(int n , int a[]) {
int sum , thissum;
int i;
sum = 0;
thissum = 0;
for(i=0 ; i<n ; i++) {
thissum += a[i];
if(thissum < 0 ) {
thissum = 0;
}
if(thissum > sum) {
sum = thissum;
}
}
return sum;
}
int main() {
int n , a[100];
int i;
int sum;
printf("请输入元素的个数:\n");
scanf("%d",&n);
printf("请输入各个元素:\n");
for(i=0 ; i<n ; i++) {
scanf("%d" , &a[i]);
}
sum = MaxSumthree(n , a);
printf("分治法求得的最大子段和为:%d\n" , sum);
return 0;
}
本文探讨如何使用动态规划求解最大子段和问题。通过分析三种可能的情况,包括子段和位于左半边、右半边以及跨越两边,详细解释了递归和动态规划算法的实现。动态规划方法中,通过s[i]表示以a[i]为结尾的最大子段和,并根据s[i-1]的状态决定s[i]的计算方式,最终找到最大值。

2551

被折叠的 条评论
为什么被折叠?



