求解最大子数组问题的三种方法

来源:互联网 发布:淘宝阿里妈妈在哪里 编辑:程序博客网 时间:2024/05/20 05:59

求解最大子数组问题的三种方法

标签(空格分隔): 算法 分治 最大子数组



算法导论中有这样一个例子来引出最大子数组问题:
在股市中,人们为了获取更大利益,希望“低价买进,高价卖出”,从而获得最大收益。然而,简单的以最低价格买入,最高价格卖出并不能获得最大收益。我们可以不直接观察每日股票的价格,而是考虑每日股票的价格变化值。第i天的价格变化值定义为第i天的价格减去第i1天的价格。如果将这些值看成一个数组A,那么,问题就是求A的和最大的非空连续子数组,即最大子数组(maximum subarray)。

  • 输入:数组A={13,3,25,20,3,16,23,18,20,7,12,5,22,15,4,7}
  • 输出:最大子数组的和 43

  • 暴力解法

    • 算法思想
      枚举数组中所有连续子序列的和,并求出其中的最大值。
    • 代码

      int max_sub_array_tolient(int a[],int n){int this_sum;int max_sum = 0;for(int i=0;i<n;i++){    for(int j=i;j<n;j++){    this_sum = 0;    for(int k = i;k<=j;k++){        this_sum += a[k];        if(this_sum>max_sum)            max_sum = this_sum;    }}}return max_sum;}
    • 算法分析
      很明显,上述代码嵌套三层循环,需要的时间复杂度为O(n3).

  • 分治解法
    分治法(divide andconquer)是一种基于多分枝递归的算法范式。顾名思义,分治就是“分而治之”,即将原问题分解为两个或多个子问题,直到分解为不能分解的子问题,然后将这些问题合并成原问题的解。

    • 算法思想
      假设我们要寻找A[low...high]的最大子数组。我们需要将子数组划分为两个规模大致相等的子数组。即我们将数组A[low...high]划分为A[low...mid]A[mid+1...high],其中mid=(low+high)/2。那么,A[low...high]中所有连续子数组A[i...j]所处的位置一定是下面三种情况之一。
      • 完全位于子数组A[low...mid]中,其中,low<=i<=j<=mid
      • 完全位于子数组A[mid+1...high]中,其中,mid+1<=i<=j<=high
      • 既位于子数组A[low...mid],又位于A[mid+1...high]中,其中low<=i<=mid<=j<=high.

由上述知,最大子数组必然位于子数组A[low...mid],或子数组A[mid+1...high],或跨越中点(mid)的子数组中。我们可以递归的求解子数组A[low...mid]与子数组A[mid+1...high]的最大子数组。最后,求跨越中点(mid)的最大子数组。
跨越中点的子数组一定由A[i...mid]A[mid+1...j]组成。所以我们只需分别求出形如A[i...mid]A[mid+1...j]的最大子数组,然后将其合并。

 - 代码
    int max_sub_array(const int a[],int left,int right){    if(left == right){        if(a[left]>0)            return a[left];        else            return 0;    }    int mid = (left+right)/2;    //recursion    int max_sum_left = max_sub_array(a,left,mid);    int max_sum_right = max_sub_array(a,mid+1,right);    int left_sum = 0;    int sum = 0;    for(int i = mid;i>=left;i--){        sum += a[i];        if(sum>left_sum)            left_sum = sum;    }    int right_sum = 0;    sum = 0;    for(int i = mid+1;i<=right;i++){        sum += a[i];        if(sum>right_sum)            right_sum = sum;    }    return max(max_sum_left,max_sum_right,left_sum+right_sum);}
  • 算法分析
    现在我们来分析上述算法的时间复杂度。假设总的耗费时间为T(n)
    由上述代码,第2~8行耗费常数时间,即O(1)。第10~11行,求解n/2个元素的子数组所耗费的时间为T(n/2)。第13~27行,求解跨越中点(mid)的最大子数组的时间复杂度为O(n).所以,T(n)=O(1)+2T(n/2)+O(n)。最后,求解该递归式,得时间复杂度为O(nlgn)

  • online algorithm

利用online algorithm的思想,可以在线性时间内求得这个问题的解。

  • 算法思想:

    从数组第一个元素开始,扫描数组,记录到目前为止处理过的最大数组。若已知A[1...j]的最大子数组,那么,下一步求A[1...j+1]的最大子数组。此时,有两种情况:

    A[1...j+1]的最大子数组就是A[1...j]的最大子数组;
    A[1...j+1]的最大子数组为某个子数组A[i...j+1](1<=i<=j+1)

在知道A[1...j]的最大子数组情况下,可以在线性时间内找出形如A[i...j+1]的最大子数组。具体实现,看下面代码。

  • 代码

    int max_subarray_online(int a[],int n){int max_sum = 0;int this_sum = 0;for(int j = 0;j<n;j++){    this_sum += a[j];    if(this_sum>max_sum){        max_sum = this_sum;    }else if(this_sum < 0){        this_sum = 0;    }}return max_sum;}
  • 算法分析

很明显,上述算法的时间复杂度为O(n)


  • 参考资料
    1:Introduction to Algorithms
    [2]:Data Structures and Algorithm Analysis in C

    [2]:Introduction to Algorithms

0 0
原创粉丝点击