码迷,mamicode.com
首页 > 编程语言 > 详细

最大和子数组

时间:2015-11-18 19:28:05      阅读:578      评论:0      收藏:0      [点我收藏+]

标签:

最大和子数组问题

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
13 -3 -25 20 -3 -16 -23 18 20 -7 12 -5 -22 15 -4 7

求这个数组中子数组的最大和。

分治法的思想:

我们来思考如何用分治法来求解最大子数组问题。假定我们要寻找子数组A[low,...,high]的最大子数组。使用分治技术,意味着我们要将子数组划分为两个规模尽量相等的子数组。也就是说,找到子数组的中央位置,比如mid,然后考虑求解两个子数组A[low,...,mid]和A[mid+1,..,high]。A[low,...,high]的任何连续子数组A[i,...,j]所处的位置必然是一下三种情况之一:

  • 完全位于子数组A[low,...,mid]中,因此lowijmid
  • 完全位于子数组A[mid+1,..., high]中,一次mid<ijhigh
  • 跨越了中点,因此lowimid<jhigh
    因此,A[low,...,high]的一个最大子数组所处的位置必然是这三种情况之一。实际上A[low,...,high]的最大子数组必然是完全位于A[low,...,mid],完全位于A[low+1,...,high]中或者跨越中点的所有子数组中和最大者。我们可以递归求解A[low,...,high]和A[mid+1,..,high]的最大子数组,因为这两个子问题仍然是最大子数组问题,只是规模更小。因此,剩下的全部工作就是寻找跨越中点的最大子数组,然后再三种情况中选择最大者。

寻找跨越中点的最大子数组(O(N))伪代码:

  1. find_max_crossing_subarray(a,low,mid,hight)
  2. left_sum = -65535
  3. sum = 0
  4. for i = mid dwonto low
  5. sum = sum + A[i]
  6. if sum > left_sum
  7. left_sum = sum
  8. max_left = i
  9. right_sum = -65535
  10. sum = 0
  11. for j=mid+1 to high
  12. sum = sum +A[j]
  13. if sum>right_sum
  14. right_sum = sum
  15. max_right = j
  16. return(max_left,max_right,left_sum+right_sum)

有了一个线性时间的find_max_crossing_subarray,我们就可以设计求解最大子数组问题的分治算法的伪代码了:

  1. find_max_sumarray(A,low,high)
  2. if high==low
  3. return(low,high,A[low]) //base case:only one element
  4. else mid = (low+high)/2
  5. (left_low,left_high,left_sum)= find_max_sumarray(A,low,mid)
  6. (cross_low,cross_high,right_sum) = find_max_sumarray(A,mid+1high)
  7. if left_sum >= right_sum and left_sum >= cross_sum
  8. return (left_low,left_high,left_sum)
  9. elseif right_sum >= left_sum and right_sum >= cross_sum
  10. return (right_low,right_hight,right_sum)
  11. else return (cross_low,cross_high,cross_sum)

分治算法的时间分析:

T(n)={Θ(1)2T(n/2)+Θ(n)ifn=1ifn>1

求解为T(n)=Θ(nlgn)
我们利用分治法得到了一个渐进复杂性优于暴力求解方法的算法。通过归并排序和本次的最大子数组问题,我们开始对分支方法的强大能有了一些了解。有时,对某个问题,分支方法能给出最快的算法,而其他时候,我们(不用分治方法)甚至能做得更好。以此为例,不用分治法,而用动态规划的思想求解本题能得到线性时间复杂度的算法。

非递归的思考

使用如下思想为最大子数组问题设计一个非递归的,线性时间的算法。从数组的左边界开始,由左至右处理,记录到目前为止已经处理过的最大子数组。若已知A[1,...,j]的最大子数组,基于如下性质将解扩展为A[1,..,j+1]的最大子数组:A[1,..,j+1]的最大子数组要么是A[1,..,j]的最大子数组,要么是某个子数组A[i,...,j+1](1<=i<=j+1)。在已知A[1,..,j]的最大子数组的情况下,可以在线性时间内找出形如A[i,...j+1]的最大子数组。
怎么找出来呢?

  1. 一个O(n)的算法:
    F[j]为A[1..j]数组的最大和子数组。
    我只想到
    F[j]=max1ij{F[j?1],A[i,..,j]}

    如果直接这样编程,时间就不是线性的了。如果我们稍作修改呢?
    Sum[0,0]=0;
    A[i,...,j]=Sum[0,...,j]?Sum[0,...,i]?F[j]=max{F[j?1],Sum[0,..,j]?min0ijSum[0,..i]}

    我们可以先计算出min0ijSum(0,..i)来,
    sum(i)=Sum[0,...i],sum(0)=0
    则:sum(i)=sum(i?1)+A[i],1ii
    可以得到以下步骤:
    1. 求前缀数组和以及最小和前缀
      • 定义:前缀和sum[i] = A[1]+...+A[i],sum[0]=0。
        并且定义: m[i]是A[1,...,i]子数组中最小和的前缀串。m[0] = 0.
      • 则:sum[j]=sum[j?1]+A[j].
        顺带求出m[j]=min(m[j?1],sum[j])
    2. F[j]=max{F[j?1],sum[j]?m[j]}

      因为每一步都是线性的,所以总的时间复杂是O(n)。
      可以看到其实sum[j] - m[j]就是以A[j]结尾的子数组最大和。如果我们把这部分单独拿出来考虑,可以得到另一种方法:
  2. 另一个稍作改进的算法
    既然sum[j]-m[j]是以A[j]结尾的最大子数组和S[j],那么我们求出所有的S[1],...,S[n],然后求一个最大的,不就得到解了么?
    1. 求前缀数组和以及最小和前缀
      • 定义:前缀和sum[i] = A[1]+...+A[i],sum[0]=0。
        并且定义: m[i]是A[1,...,i]子数组中最小和的前缀串。m[0] = 0.
      • 则:sum[j]=sum[j?1]+A[j].
        顺带求出m[j]=min(m[j?1],sum[j])
        那么,S[j] = sum[j] - m[j]
    2. 求出S[i,...,N]的最大值

利用动态规划来解决

最优子结构:
设S[j]是以元素A[j]结尾的和最大子数组。那么已知j以前的状态,S[0],..,S[j-1]怎么求S[j]呢?
其实

S[j]=max{S[j?1]+A[j],A[j]}

重叠子问题:
如果直接这样递归的去做,肯定会重复计算很多次相同的子问题。
所以我们改成迭代,经过O(n)时间复杂度就可以求出答案。
*代码如下:

  1. /*求最大子数组的和,以及返回这个数组本身*/
  2. int MaxSubarray(const int * a, int size, int & from, int & to){
  3. if (!a || (size <= 0)){
  4. from = to = -1;
  5. return 0;
  6. }
  7. from = to = 0;
  8. int sum = a[0];
  9. int result = sum;
  10. int fromNew = 0; // 新的子数组起点
  11. for (int i = 0; i < size; i++){
  12. if (sum>0){
  13. sum += a[i];
  14. }
  15. else{
  16. sum = a[i];
  17. fromNew = i;
  18. }
  19. if (result < sum){
  20. result = sum;
  21. from = fromNew;
  22. to = i;
  23. }
  24. }
  25. return result;
  26. }




最大和子数组

标签:

原文地址:http://www.cnblogs.com/gavin-yue/p/4975566.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!