1. 程式人生 > >最大連續子數列和(線上處理演算法)

最大連續子數列和(線上處理演算法)

問題描述

最大連續子數列和一道很經典的演算法問題,給定一個數列,其中可能有正數也可能有負數,我們的任務是找出其中連續的一個子數列(不允許空序列),使它們的和儘可能大。我們一起用多種方式,逐步優化解決這個問題。

暴力方法

求出所有可能連續子列的和,時間複雜度O(N^3)

int MaxSubSequm1(int A[], int N)
{
    int ThisSum, MaxSum = 0;
    int i, j, k;
    for (i = 0; i < N; i++)  {  // i 是子列左端的位置
        for (j = i; j < N; j++){ //j 是子列右端的位置
ThisSum = 0; //* for (k = i; k <= j; k++) //* ThisSum += A[k]; //* if (ThisSum>MaxSum) // 如果剛得到的子列和更大 MaxSum = ThisSum; // 更新結果 } }// 迴圈結束 return MaxSum; }

一個簡單的優化

很顯然上面的演算法中,在計運算元列和時,可以利用前一步計算和的結果,不需要每次從頭累加。時間複雜度O(N^2)。

int MaxSubSequm1(int A[], int N)
{
    int ThisSum, MaxSum = 0;
    int i, j, k;
    for (i = 0; i < N; i++)  {  // i 是子列左端的位置
        for (j = i; j < N; j++){ //j 是子列右端的位置
            ThisSum +=A[j];               //*
                  // 對於相同i ,的不同j ,只要在j-1次迴圈的基礎上累加1 項即可

            if (ThisSum>MaxSum)  // 如果剛得到的子列和更大
MaxSum = ThisSum; // 更新結果 } } return MaxSum; }

分而治之

  1. 將問題一分為二,縮小問題規模,遞迴求解。
  2. 此處求解過程中要從中間基準點開始,掃描求出跨界的最大連續子列和,然後和左右邊的解比較求出最終的結果。
  3. 時間複雜度O(N*logN)
int Max3( int A, int B, int C )
    { /* 返回3個整數中的最大值 */
        return A > B ? A > C ? A : C : B > C ? B : C;
    }

    int DivideAndConquer( int List[], int left, int right )
    { /* 分治法求List[left]到List[right]的最大子列和 */
        int MaxLeftSum, MaxRightSum; /* 存放左右子問題的解 */
        int MaxLeftBorderSum, MaxRightBorderSum; /*存放跨分界線的結果*/

        int LeftBorderSum, RightBorderSum;
        int center, i;

        if( left == right )  { /* 遞迴的終止條件,子列只有1個數字 */
            if( List[left] > 0 )  return List[left];
            else return 0;
        }

        /* 下面是"分"的過程 */
        center = ( left + right ) / 2; /* 找到中分點 */
        /* 遞迴求得兩邊子列的最大和 */
        MaxLeftSum = DivideAndConquer( List, left, center );
        MaxRightSum = DivideAndConquer( List, center+1, right );

        /* 下面求跨分界線的最大子列和 */
        MaxLeftBorderSum = 0; LeftBorderSum = 0;
        for( i=center; i>=left; i-- ) { /* 從中線向左掃描 */
            LeftBorderSum += List[i];
            if( LeftBorderSum > MaxLeftBorderSum )
                MaxLeftBorderSum = LeftBorderSum;
        } /* 左邊掃描結束 */

        MaxRightBorderSum = 0; RightBorderSum = 0;
        for( i=center+1; i<=right; i++ ) { /* 從中線向右掃描 */
            RightBorderSum += List[i];
            if( RightBorderSum > MaxRightBorderSum )
                MaxRightBorderSum = RightBorderSum;
        } /* 右邊掃描結束 */

        /* 下面返回"治"的結果 */
        return Max3( MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum );
    }

    int MaxSubseqSum3( int List[], int N )
    { /* 保持與前2種演算法相同的函式介面 */
        return DivideAndConquer( List, 0, N-1 );
    }

下面是一個過程演示圖:
這裡寫圖片描述
這裡最大的連續子列和是11。

線上處理

我們可以通過拋棄負子列,保證最大子列和遞增。當掃描一遍,最大子列和不再遞增時,當前的最大子列和即為我們的解。這是最優演算法,時間複雜度O(N)。

int MaxSubseqSum4(int A[], int N)
{
    int ThisSum, MaxSum;
    int i;
    ThisSum = MaxSum = 0; 
    for (i = 0; i < N; i++){ 
        ThisSum += A[i];//向右累加
        if (ThisSum>MaxSum)
            MaxSum = ThisSum; // 發現更大和則更新當前結果
        else if (ThisSum < 0)  // 如果當前子列和為負數
            ThisSum = 0; // 則不可能使後面部分和增大,拋棄之
    }
    return MaxSum;

}