最大子段和问题

最大子段和问题(Maximum Interval Sum) 经典的动态规划问题,几乎所有的算法教材都会提到.本文将分析最大子段和问题的几种不同效率的解法,以及最大子段和问题的扩展和运用.

      一.问题描述

      给定长度为n的整数序列,a[1...n], 求[1,n]某个子区间[i , j]使得a[i]+…+a[j]和最大.或者求出最大的这个和.
      例如(-2,11,-4,13,-5,2)的最大子段和为20,所求子区间为[2,4].
      如果该序列的所有元素都是负整数时定义其最大子段和为0。

      二. 问题分析

      1、最大子段和问题的简单算法:
For each element in the array, we want to track the subarray with the largest sum ending here. And the largest of those largest sums is the result we need. So how do we track the largest sum ending at a particular position? We can keep adding up positive numbers. We can also add negative numbers as long as the sum would be less than 0. If it’s smaller than 0, we need to reset from this position by starting the sum from 0. In the above example, the largest sums ending at each position are [-2,1,-3,4,3,5,6,1,5]. Hence the largest sum is 6.
class Solution {
public:
    int maxSubArray(int A[], int n) {
        int maxSumSoFar = INT_MIN;
        int maxSumEndingHere = 0;
        for(int i = 0; i < n; ++i) {
            maxSumEndingHere = max(maxSumEndingHere+A[i], A[i]);
            maxSumSoFar = max(maxSumSoFar, maxSumEndingHere);
        }
        return maxSumSoFar;
    }
};

      2、最大子段和问题的分治法:

      求子区间及最大和,从结构上是非常适合分治法的,因为所有子区间[start, end]只可能有以下三种可能性:
      在[1, n/2]这个区域内
      在[n/2+1, n]这个区域内
      起点位于[1,n/2],终点位于[n/2+1,n]内
      //最大子段和,分治算法。T(n)=O(nlog(n))。
      #include<iostream>
      using namespace std;
      int MaxSubSum(int a[],int left,int right)
      {
          int sum = 0;
          if(left == right)
              sum = a[left] > 0 ? a[left] : 0;
          else
          {
              int center = (left + right) / 2;
              int leftsum = MaxSubSum(a, left, center);
              int rightsum = MaxSubSum(a, center + 1, right);
              int s1 = 0;
              int lefts = 0;
              for(int i = center; i >= left; i--)
              {
                  lefts += a[i];
                  if(lefts > s1)
                      s1 = lefts;
              }
              int s2 = 0;
              int rights = 0;
              for(int i = center + 1; i <= right; i++)
              {
                  rights += a[i];
                  if(rights > s2)
                      s2 = rights;
              }
              sum = s1 + s2;
              if(sum < leftsum)
                  sum = leftsum;
              if(sum < rightsum)
                  sum = rightsum;
          }
          return sum;
      }
      int main()
      {
          int n,a[100],m,maxsum;
          cout<<"请输入整数序列的元素个数n:"<<endl;
          cin>>n;
          cout<<"请输入序列中各元素的值a[i](一共"<<n<<"个)"<<endl;
          for(m=0;m<n;m++)
              cin>>a[m];
          int b[100];
          for(m=0;m<n;m++)
              b[m+1]=a[m];
          maxsum=MaxSubSum(b,1,n);
          cout<<"整数序列的最大子段和是:"<<maxsum<<endl;
          system("pause");
      }


      数据测试:
      请输入整数序列的元素个数n:
      6
      请输入序列中各元素的值a[i](一共6个)
      -2 11 -4 13 -5 -2
      整数序列的最大子段和是:20
      请按任意键继续...
      
      分治法的难点在于第三种情形的理解,这里应该抓住第三种情形的特点,也就是中间有两个定点,然后分别往两个方向扩张,以遍历所有属于第三种情形的子区间,求的最大的一个,如果要求得具体的区间,稍微对上述代码做点修改即可. 分治法的计算时间复杂度为O(nlogn).

      3、最大子段和问题的动态规划算法:
      令b[j]表示以位置 j 为终点的所有子区间中和最大的一个
      子问题:如j为终点的最大子区间包含了位置j-1,则以j-1为终点的最大子区间必然包括在其中
      如果b[j-1] >0, 那么显然b[j] = b[j-1] + a[j],用之前最大的一个加上a[j]即可,因为a[j]必须包含
      如果b[j-1]<=0,那么b[j] = a[j]。

      对于这种子问题结构和最优化问题的证明,可以参考算法导论上的“剪切法”,即如果不包括子问题的最优解,把你假设的解粘帖上去,会得出子问题的最优化矛盾.证明如下:
      令a[x,y]表示a[x]+…+a[y] , y>=x
      假设以j为终点的最大子区间 [s, j] 包含了j-1这个位置,以j-1为终点的最大子区间[ r, j-1]并不包含其中
      即假设[r,j-1]不是[s,j]的子区间
      存在s使得a[s, j-1]+a[j]为以j为终点的最大子段和,这里的 r != s 
      由于[r, j -1]是最优解, 所以a[s,j-1]<a[r, j-1],所以a[s,j-1]+a[j]<a[r, j-1]+a[j]
      与[s,j]为最优解矛盾.
      参考代码如下:
      //最大子段和,动态规划,T(n)=O(n)。
      #include<iostream>
      using namespace std;
      int MaxSum(int n,int a[])
      {
          int sum = 0;
          int b = 0;
          for(int i = 1; i <= n; i++)
          {
              if(b > 0)
                  b += a[i];
              else
                  b = a[i];
              if(b > sum)
                  sum = b;
          }
          return sum;
      }
      int main()
      {
          int n,a[100],m,maxsum;
          cout<<"请输入整数序列的元素个数n:"<<endl;
          cin>>n;
          cout<<"请输入序列中各元素的值a[i](一共"<<n<<"个)"<<endl;
          for(m=0;m<n;m++)
              cin>>a[m];
          int b[100];
          for(m=0;m<n;m++)
              b[m+1]=a[m];
          maxsum=MaxSum(n,b);
          cout<<"整数序列的最大子段和是:"<<maxsum<<endl;
          system("pause");
          }
      数据测试:
      请输入整数序列的元素个数n:
      6
      请输入序列中各元素的值a[i](一共6个)
      -2 11 -4 13 -5 -2
      整数序列的最大子段和是:20
      请按任意键继续...

      我们总结一下二维最大子段和问题,以及最大m段和问题.

      二维最大子段和问题又称为最大子矩阵问题,给定一个m行n列的整数矩阵A,试求A的一子矩阵,使其各元素之和最大。
      问题分析:
      动态规划法其实就是把二维最大子段和转化为一维最大子段和问题.

      转化方法:
      我们把这个矩阵划分成n个“条”,条的长度为1到m,通过两个for遍历所有长度的条
      然后,若干个连续的条,就是一个子矩阵了,这样问题就轻易地转化为一维最大子段和问题了
      通过求所有这种条,起点为i,长度为1到m-i+1的“条”的最大子段和,就可以求出整个矩阵的最大子矩阵了
      具体枚举长条的时候,同一起点的长度,由于“条”的不同长度间可以利用之前的结果
      比如令b[k][i][j]表示第k个长“条”区间从i到j的和,那么b[k][i][j+1] = b[k][i][j]+a[j][k]
      当然,实际编程的时候,由于之前的结果求完一维最大子段和后,便不需要保存,所以只需要一维数组b即可
      参考代码如下:
     //标准的一维最大子段和
      int maxSubInterval(int *data, int n)
      {
          int max = 0;
          int b = 0;
          for(int i = 0; i != n; ++i)
          {
             if(b > 0)
             {
                 b = b + data[i];
             }
             else
             {
                 b = data[i];
             }
             if(b > max)
                max = b;
          }
          return max;
      }

      int maxSubMatrix(int (*a)[10], int m, int n)
      {
          int max = 0;
          //b[k]记录第k个“条”的和
          int *b = new int[n + 1];
          for(int i = 0; i != m; ++i)
          {
              //“条”的和先置为0
              for(int k = 0; k != n; ++k)
                  b[k] = 0;
              //起点为i,长度为j-i+1的条
              //相同起点,长度为k的“条”的和,等于长度为k-1的“条”的和加上当前元素a[j][k]
              for(int j = i; j != m; ++j)
              {
                  for(int k = 0; k != n; ++k)
                      b[k] += a[j][k];
                  int sum = maxSubInterval(b, n);
                  if(sum > max)
                      max = sum;
              }
          }
          free(b);
          return max;
      }


没有更多推荐了,返回首页