动态规划、分治法、蛮力法求解最大子段和

问题定义: 给定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;
}

动态规划法

  1. 若记b[j]=max(a[i]+a[i+1]+…+a[j]),其中1<=i<=j,并且1<=j<=n。则所求的最大子段和为max b[j],1<=j<=n。
  2. 由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)
这里写图片描述

  • 18
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值