连续子数组最大和问题编程实现

       引自 http://www.cnblogs.com/en-heng/p/3970231.html

        连续子数组最大和问题

问题描述

输入一个整形数组,求数组中连续的子数组使其和最大。比如,数组x

应该返回 x[2..6]的和187.

问题解决

我们很自然地能想到穷举的办法,穷举所有的子数组的之和,找出最大值。

穷举法

i, j的for循环表示x[i..j],k的for循环用来计算x[i..j]之和。

maxsofar = 0
for i = [0, n)
    for j = [i, n)
        sum = 0
        for k = [i, j]
            sum += x[k]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

有三层循环,穷举法的时间复杂度为 O(n3)

对穷举法的改进1

我们注意到x[i..j]之和 = x[i..j-1]之和 + x[j],因此在j的for循环中,可直接求出sum。

maxsofar = 0
for i = [0, n)
    sum = 0
    for j = [i, n)
        sum += x[j]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

显然,改进之后的时间复杂度变为 O(n2)

对穷举法的改进2

在计算fibonacci数时,应该还有印象:用一个累加数组(cumulative array)记录前面n-1次之和,计算当前时只需加上n即可。同样地,我们用累加数组cumarr记录:cumarr[i] = x[0] + . . . +x[i],那么x [i.. j]之和 = cumarr[j] -cumarr[i - 1]

cumarr[-1] = 0
for i = [0, n)
    cumarr[i] = cumarr[i-1] + x[i]
    
maxsofar = 0
for i = [0, n)
    for j = [i, n)
        sum = cumarr[j] - cumarr[i-1]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

时间复杂度依然为 O(n2)

分治法

所谓分治法,是指将一个问题分解为两个子问题,然后分而解决之。具体步骤如下:

  • 先将数组分为两个等长的子数组a, b;

  • 分别求出两个数组a,b的连续子数组之和;

  • 还有一种情况比较容易忽略:有可能最大和的子数组跨越两个数组;

  • 最后比较 ma mb mc ,取最大即可。

在计算 mc 时,注意: mc 必定包含总区间的中间元素,因此求 mc 等价于从中间元素开始往左累加的最大值 + 从中间元素开始往右累加的最大值

float maxsum3(l, u)
    if (l > u) /* zero elements */
        return 0
        
    if (l == u) /* one element */
        return max(0, x[l])
    
    m = (l + u) / 2
    /* find max crossing to left */
    lmax = sum = 0
    for (i = m; i >= l; i--)
        sum += x[i]
        lmax = max(lmax, sum)
    
    /* find max crossing to right */
    rmax = sum = 0
    for i = (m, u]
        sum += x[i]
        rmax = max(rmax, sum)

    return max(lmax+rmax,
                maxsum3(l, m),
                maxsum3(m+1, u));

容易证明,时间复杂度为 O(nlog n)

Kadane算法

Kadane算法又被称为扫描法,该算法用到了一个启发式规则:如果前面一段连续子数组的和小于0,那么就丢弃它。其实也蛮好理解的,举个简单例子,比如:数组-1, 2, 3,-1为负数,为了使得子数组之和最大,显然不应当把-1计入进内。

max_ending_here记录前面一段连续子数组之和。

Initialize:
    max_so_far = 0
    max_ending_here = 0

Loop for each element of the array
  (a) max_ending_here = max_ending_here + x[i]
  (b) if(max_ending_here < 0)
            max_ending_here = 0
  (c) if(max_so_far < max_ending_here)
            max_so_far = max_ending_here
return max_so_far

只遍历了一遍数组,因此时间复杂度为 O(n)

编程实现

#include <iostream>
#include <fstream>
#include <vector>

using namespace std;

vector<int> s;
int maxSoFar;

int maxSum3(int l, int u, vector<int> x);//分治法函数声明

int main()
{

    ifstream in("input.txt");
    for(int i;in>>i;)
        s.push_back(i);

    int sum;
    maxSoFar = 0;

    //穷举法方法一
    for (int i = 0;i<s.size()-1;i++){
        for (int j = i;j<s.size()-1;j++){
                sum = 0;

                for(int k=i;k<j;k++)
                    sum += s[k];
                /* sum is sum of x[i..j] */

                maxSoFar = max(maxSoFar, sum);
        }
    }
    cout << maxSoFar<< endl;

    //穷举法之改进1:在j的for循环中,可直接求出sum。
    for (int i = 0;i<s.size()-1;i++){
        sum = 0;
        for (int j = i;j<s.size()-1;j++){
                sum += s[j];
                maxSoFar = max(maxSoFar, sum);
        }
    }
    cout << maxSoFar<< endl;


    //穷举法之改进2:在计算fibonacci数时,我们用一个累加数组(cumulative array)记录前面n-1次之和,计算当前时只需加上n即可。
    //同样地,我们用累加数组cumArr记录
    vector<int> cumArr(s.size());
    for (int i=0;i<s.size();i++)
        cumArr[i] = cumArr[i-1] + s[i];

    maxSoFar = 0;

    for (int i= 0;i<s.size();i++)
        for(int j = i;j<s.size();j++){
            sum = cumArr[j] - cumArr[i-1];

            maxSoFar = max(maxSoFar, sum);
        }
    cout << maxSoFar<< endl;
//==================================================

    // 分治法:可以看成递归的把每个元素假设成最大子数组中元素,并由它向左或向右查找
    maxSoFar=maxSum3(0,s.size()-1,s);

    cout << maxSoFar<< endl;
//==================================================

    //Kadane算法,即扫描法,该算法用到了一个启发式规则:如果前面一段连续子数组的和小于0,那么就丢弃它。
    maxSoFar = 0;
    int maxEndingHere = 0;

    for(int i=0;i<s.size();i++){
        maxEndingHere = maxEndingHere+ s[i];
        if(maxEndingHere < 0)//如果当前子序列小于0了,又重新开始扫描。
            maxEndingHere = 0;
        if(maxSoFar< maxEndingHere)
            maxSoFar= maxEndingHere;
    }
    cout << maxSoFar<< endl;

    return 0;
}



//===============================================
// 分治法
int maxSum3(int l, int r, vector<int> x){
        if (l > r) /* zero elements */
            return 0;
        if (l == r) /* one element */
            return max(0, x[l]);

        int m = (l + r) / 2;
        
        //从中间向两边寻找,能够实现分治求解,并同时查找横跨两个子问题的最大子数组和
        /* find max crossing to left */
        int lmax = 0,sum = 0;
        for (int i = m; i >= l; i--){//保证可以从中间开始,找横跨两个子问题的最大子数组和。
            sum += x[i];
            lmax = max(lmax, sum);
        }
        /* find max crossing to right */
        int rmax = 0;
        sum = 0;
        for (int i = m+1;i<=r;i++){
            sum += x[i];
            rmax = max(rmax, sum);
        }

        return max(lmax+rmax,//横跨两个子问题的子数组
                    max(maxSum3(l,m,x),maxSum3(m+1,r,x)));
    }



参考资料

[1] Jon Bentley, Programming Pearls.
[2] GeeksforGeeks, Largest Sum Contiguous Subarray.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值