求连续子段最大和

# 问题:

从一组数据(长度为 n,其中 n <= 10000,数据的值都大于 -60000)中找出连续的一段数,使得这段数的和最大。

输入描述:第一行是一个正整数 n,表示数据的个数,从第二行开始是 n 个数据。

输出描述:一行,子段的最大和。

输入样例:

5
1 -3 4 1 -9

输出样例:

5

思考:

采用分而治之的策略。每次将序列分成左右两个部分,最大子序列的在以下三种情况产生:
1.最大子序列在左边产生 S1;
2.最大子序列在右边产生 S2;
3.最大子序列横跨左右两部分 S3。

最大子序列=max{S1,S2,S3}。

在每个划分中,都可以采用上述的划分情况得出局部最大子序列和。

那么接下来的问题便是如何求S1,S2,S3呢?

显而易见,采用分而治之的策略划分序列后,那么划分到最后,最后的子序列中将只有一个元素,此时S就直接是这个元素的值。
而在这个只有一个元素子序列的上一层(即他的父结点),会有两个左右结点,因此对于这个父结点的子序列而言,他的S1就是它左孩子的最大子序列和S,他的S2就是它右孩子的最大子序列和S。
接下来我们就需要考虑如何求S3了,前面提到过S3是横跨左右两部分的,所以我们可以沿着分割左右两块的那条分割线,先求分割线左侧的最大值left,再求右侧的最大值right,S3=left+right。注意,这里的left和right与上面的s1,s2不相同。这里的left的必须从分割线左侧的第一个数开始,向左延伸相加,不可以间断。right也是同理。基于这个性质,我们可以对此求解。
定义变量

int left, right, temp;
    left = right = -99999;
    temp = 0;

求left

 for (int i = mid; i >= low; i--)
    {
        temp += a[i];
        if (temp > left)
            left = temp;
    }

求right和S3

temp = 0;
    for (int i = mid + 1; i <= high; i++)
    {
        temp += a[i];
        if (temp > right)
            right = temp;
    }
    return right + left;

一个例子:

代码

// 题目描述:
// 从一组数据(长度为 n,其中 n <= 10000,数据的值都大于 -60000)中找出连续的一段数,使得这段数的和最大。
// 输入描述:
// 第一行是一个正整数 n,表示数据的个数,从第二行开始是 n 个数据。
// 输出描述:
// 一行,子段的最大和。
// 样例输入:
// 5
// 1 -3 4 1 -9
// 样例输出:
// 5

#include <iostream>
using namespace std;

int smax(int a[], int low, int high);
int crossingsmax(int a[], int low, int mid, int high);
int main()
{
    int n;
    while (cin >> n)
    {
        int a[n + 1];
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        cout << smax(a, 1, n) << endl;
    }
    return 0;
}

int smax(int a[], int low, int high)
{
    int mid, s1, s2, s3, s;
    if (low == high)
        return a[low];
    else
    {
        mid = (low + high) / 2;
        s1 = smax(a, low, mid);
        s2 = smax(a, mid + 1, high);
        s3 = crossingsmax(a, low, mid, high);
        s = s1 > s2 ? (s1 > s3 ? s1 : s3) : (s2 > s3 ? s2 : s3);
        return s;
    }
}

int crossingsmax(int a[], int low, int mid, int high)
{
    int left, right, temp;
    left = right = -99999;
    temp = 0;
    for (int i = mid; i >= low; i--)
    {
        temp += a[i];
        if (temp > left)
            left = temp;
    }
    temp = 0;
    for (int i = mid + 1; i <= high; i++)
    {
        temp += a[i];
        if (temp > right)
            right = temp;
    }
    return right + left;
}
posted @ 2022-03-17 18:24  请去看诡秘之主  阅读(72)  评论(0)    收藏  举报