问题定义: 给定n个整数9可能为负数)组成的序列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。
首先需要一个方法来产生一个一维数组
#include <stdio.h>
#include <stdlib.h>
/**
* 产生一个数组
* @param a 空数组
* @return 数组长度count
*/
int produceSZ(int a[]) {
int i;
int count;
printf("请输入数组个数:\n");
scanf("%d", &count);
for (i = 0; i < count; i++) {
printf("请输入第%d个数\n", i + 1);
scanf("%d", &a[i]);
}
for (i = 0; i < count; i++) {
printf("%d\t", a[i]);
}
printf("\n");
return count;
}
动态规划法
- 若记b[j]=max(a[i]+a[i+1]+…+a[j]),其中1<=i<=j,并且1<=j<=n。则所求的最大子段和为max b[j],1<=j<=n。
- 由b[j]的定义可易知,当b[j-1]>0时b[j]=b[j-1]+a[j],否则b[j]=a[j]。故b[j]的动态规划递归式为:
b[j]=max(b[j-1]+a[j],a[j]),1<=j<=n
代码
/**
* 动态规划法
* @param a 数组
* @param b 辅助数组
* @param len 数组长度
* @return 一维数组,包括最大子段和、起始位置、结束位置
*/
int *maxSubSum(int a[], int b[], int len) {
/*
index[0]最大子段和
index[1]起始位置
index[2]结束位置
*/
int *index;
int i;
index = (int *) malloc(sizeof(int) * 3);//申请空间
b[0] = a[0];
index[0] = b[0];//先假定b[0]为最大子段和
index[1] = 1;//起始位置的逻辑位置为1
index[2] = 1;
for (i = 1; i < len; i++) {
if (b[i - 1] > 0)
b[i] = b[i - 1] + a[i];
else {
b[i] = a[i];
index[1] = i + 1;//储存起始点的逻辑位置的坐标
}
if (b[i] > index[0]) {
index[0] = b[i];
index[2] = i + 1;//储存结束点的逻辑位置的坐标
}
}
return index;
}
分治法
最大子段和问题的分治策略是:
(1) 划分: 按照平衡子问题的原则, 将序列( a 1 , a 2 , ⋯, an )划分成长度相同的两个 子序列( a 1 , ⋯, a n / 2 )和( a n / 2 + 1 ,⋯, an ) ,则会出现以下 3 种情况:
① a1 , ⋯, an 的最大子段和 = a 1 , ⋯, a n / 2 的最大子段和;
② a1 , ⋯, an 的最大子段和 = a n / 2 + 1 , ⋯, an 的最大子段和;
③ a1 , ⋯, an 的最大子段和 = ai + ⋯ + aj, 且 1 ≤ i ≤ n / 2 , n / 2 + 1 ≤ j ≤ n。
(2) 求解子问题: 对于划分阶段的情况①和②可递归求解, 情况③需要分别计算
s1 = max(ai ⋯⋯ a[2/n]),1 ≤ i ≤ n / 2 ; s2 = max(a[n/2+1]⋯⋯a[j]) ,n / 2 + 1 ≤ j ≤ n , 则 s1 + s2 为情况 ③ 的最大子段和。
(3) 合并: 比较在划分阶段的 3 种情况下的最大子段和,取三者之中的较大者为原问 题的解。
最大子段和的分治思想
代码
/**
*分治法
* @param left 起始位置
* @param right 结束位置
* @return index[0]最大子段和、index[1]起始位置、index[2]结束位置
*/
int *maxsub(int a[], int left, int right) {
int center;//中位数
int sum;
int left_max;
int right_max;
int i;
int *index;
int *left_Sub;//情况一的最大子段和
int *right_Sub;//情况二的最大子段和
int *index_left;
int *index_right;
index = (int *) malloc(sizeof(int) * 3);//申请空间
index_left = (int *) malloc(sizeof(int) * 3);
index_right = (int *) malloc(sizeof(int) * 3);
center = (left + right) / 2;
if (left == right) {
index[0] = a[left];
index[1] = left;
index[2] = right;
} else {
left_Sub = maxsub(a, left, center);//情况1,递归求解
right_Sub = maxsub(a, center + 1, right);//情况二,递归求解
/*
* 情况三的最大子段和
*/
sum = 0;
left_max = a[center];
index_left[1] = center;
for (i = center; i >= left; i--) {
sum += a[i];
if (sum > left_max) {
left_max = sum;
index_left[0] = sum;
index_left[1] = i;
}
}
sum = 0;
right_max = a[center + 1];
index_right[2] = center + 1;
for (i = center + 1; i <= right; i++) {
sum += a[i];
if (sum > right_max) {
right_max = sum;
index_right[0] = sum;
index_right[2] = i;
}
}
/*
*比较三种情况,找出子段和数值最大的情况
*/
sum = right_max + left_max;
index[0] = sum;//先假定第三种情况的子段和最大
index[1] = index_left[1];
index[2] = index_right[2];
if (sum < left_Sub[0]) {//相互比较
index[0] = left_Sub[0];
index[1] = left_Sub[1];
index[2] = left_Sub[2];
}
if (sum < right_Sub[0]) {
index[0] = right_Sub[0];
index[1] = right_Sub[1];
index[2] = right_Sub[2];
}
}
return index;
}
蛮力法
/**
* 蛮力法
* @param a 数组
* @param len 数组长度
* @return index[0]最大子段和、index[1]起始位置、index[2]结束位置
*/
int *maxSum(int a[],int len)
{
int maxSum = 0;
int sum = 0;
int *index;
int i;
int j;
index = (int *) malloc(sizeof(int) * 3);//申请空间
for(i = 0; i < len; i++) //从第一个数开始算起
{
sum = a[i];
for(j = i + 1; j < len; j++)//从i的下一个数开始
{
a[i] += a[j];
if(a[i] > sum)
{
sum = a[i];//每一趟的最大值
if(a[i] > maxSum){
index[2] = j + 1;//储存结束点的逻辑位置的坐标
}
}
}
if(sum > maxSum)
{
maxSum = sum;
index[0] = sum;
index[1] = i + 1;//储存起始点的逻辑位置的坐标
}
}
return index;
}
测试以上方法
int main(int argc, char *argv[]) {
int a[100];//a的空间大小根据情况来定,给的稍微大些,这里给了100
int b[100];
int *max;
int *max1;
int *max2;
int len;//数组长度
len = produceSZ(a);
printf("********分治法********\n");
/*
之所以第三个参数为len-1
是因为在 计算情况三的最大子段和的时候
从center+1一直加到right(包含right)
*/
max = maxsub(a, 0, len - 1);
printf("max = %d\n", max[0]);
printf("start = %d\n", max[1]);
printf("end = %d\n", max[2]);
printf("********动态规划********\n");
max1 = maxSubSum(a, b, len);
printf("max = %d\n", max1[0]);
printf("start = %d\n", max1[1]);
printf("end = %d\n", max1[2]);
printf("********蛮力法********\n");
max2 = maxSum(a, len);
printf("max = %d\n", max2[0]);
printf("start = %d\n", max2[1]);
printf("end = %d\n", max2[2]);
return 0;
}
运行结果:
例一:(-20, 11, -4, 13, -5, -2)
例二:(8, -2, 11, -4, 13, -5, -2)
例三:(31, -41, 59, 26, -53, 58, 97, -93, -23, -84)