求解最大子數組問題的三種方法

求解最大子數組問題的三種方法

標籤(空格分隔): 算法 分治 最大子數組



算法導論中有這樣一個例子來引出最大子數組問題:
在股市中,人們爲了獲取更大利益,希望“低價買進,高價賣出”,從而獲得最大收益。然而,簡單的以最低價格買入,最高價格賣出並不能獲得最大收益。我們可以不直接觀察每日股票的價格,而是考慮每日股票的價格變化值。第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的思想,可以在線性時間內求得這個問題的解。

  • 算法思想:

    從數組第一個元素開始,掃描數組,記錄到目前爲止處理過的最大數組。若已知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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章