1. 程式人生 > >最大子段和問題:蠻力、遞迴及動態規劃

最大子段和問題:蠻力、遞迴及動態規劃

問題描述

  • 求一個序列的最大子段和即最大連續子序列之和。例如序列[4, -3, 5, -2, -1, 2, 6, -2]的最大子段和為11=[4+(-3)+5+(-2)+(-1)+(2)+(6)]。

1. 蠻力演算法

  • 思想:從序列首元素開始窮舉所有可能的子序列。
  • 程式碼示例(C++):
#include<iostream>
using namespace std;
int MaxSubsequenceSum(const int array[], int n)
{
    int tempSum, maxSum;
    maxSum = 0;
    for
(int i = 0;i < n;i++) // 子序列起始位置 { for (int j = i;j < n;j++) // 子序列終止位置 { tempSum = 0; for (int k = i;k < j;k++) // 子序列遍歷求和 tempSum += array[k]; if (tempSum > maxSum) // 更新最大和值 maxSum = tempSum; } } return
maxSum; } int main() { const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 }; int maxSubSum = MaxSubsequenceSum(a, 8); cout << "The max subsequence sum of a is: " << maxSubSum << endl; system("pause"); return 0; }
  • 演算法複雜度O(n3)

2. 改進的蠻力演算法

  • 思想:直接在劃定子序列時累加元素值,減少一層迴圈。
  • 程式碼示例(C++):
#include<iostream>
using namespace std;
int MaxSubsequenceSum(const int array[],int n)
{
    int tempSum, maxSum;
    maxSum = 0;
    for (int i = 0;i < n;i++)
    {
        tempSum = 0;
        for (int j = i;j < n;j++)
        {
            tempSum += array[j];
            if (tempSum > maxSum)
                maxSum = tempSum;
        }
    }
    return maxSum;
}

int main()
{
    const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 };
    int maxSubSum = MaxSubsequenceSum(a, 8);
    cout << "The max subsequence sum of a is: " << maxSubSum << endl;
    system("pause");
    return 0;
}
  • 演算法複雜度O(n2)

3. 分治遞迴的演算法

  • 思想:將序列劃分為左右兩部分,則最大子段和可能在三處出現:左半部、右半部以及跨越左右邊界的部分。遞迴的終止條件是:left == right。
  • 程式碼示例:
#include<iostream>
using namespace std;
int max3(int a, int b, int c)           // 求三個數的最大值
{
    int max = a;
    if (b > max)
        max = b;
    if (c > max)
        max = c;
    return max;
}

int MaxSubsequenceSum(const int array[], int left, int right)   
{
    if (left == right)          // 設定基準,即遞迴終止條件
        return array[left];

    int middle = (left + right) / 2;

    int leftMaxSubsequenceSum = MaxSubsequenceSum(array, left, middle);     // 求左半部分最大子序列和
    int rightMaxSubsquenceSum = MaxSubsequenceSum(array, middle + 1, right);    // 求右半部分最大子序列和

    // 處理左右邊界問題:最大子序列跨越中間,包含左半部分最右一個數,同時包含右半部分最左一個數
    int maxLeftBorderSum = 0;   
    int maxRightBorderSum = 0;  
    int tempSum = 0;        // 臨時求和變數
    for (int i = middle;i >= left;i--)
    {
        tempSum += array[i];
        if (tempSum > maxLeftBorderSum)
            maxLeftBorderSum = tempSum;     // 左邊包含邊界最大序列和
    }
    tempSum = 0;
    for (int i = middle + 1;i < right;i++)
    {
        tempSum += array[i];
        if (tempSum > maxRightBorderSum)
            maxRightBorderSum = tempSum;    // 右邊包含邊界最大序列和
    }

    int maxBorderSum = maxRightBorderSum + maxLeftBorderSum;        // 最大邊界子序列和等於兩部分邊界之和
    return max3(leftMaxSubsquenceSum, maxBorderSum, rightMaxSubsquenceSum);         // 返回三個部分的最大子序列和
}

int main()
{
    const int a[] = { 4, -3, 5, -2, -1, 2, 6, -2 };
    int maxSubSum = MaxSubsequenceSum(a, 0, 7);
    cout << "The max subsequence sum of a is: " << maxSubSum << endl;
    system("pause");
    return 0;
}
  • 演算法複雜度分析:假設求解N個元素序列的最大子問題的時間複雜度為T(N),則T(N)滿足:

    T(N)=2T(N/2)+O(N)T(1)=1,其中,T(N/2)表式分治後的左右兩邊求解複雜度,O(N)為求解跨越左右邊界的最大子段和的開銷。求解該遞推公式得遞迴演算法複雜度為T(N)=O(NlogN)
  • 遞迴演算法的基本準則

  • (1) 基準情形:存在最小子問題的解,也稱為遞迴終止的條件。
  • (2) 不斷推進:每一次遞迴呼叫都要使得求解狀況不斷地朝基準情形方向推進。
  • (3) 設計法則:假設所有遞迴呼叫都能執行。
  • (4) 合成效益法則:在求解一個問題的同一例項式,要避免在不同的遞迴呼叫中做重複的工作。如:遞迴求斐波那契數就是一個不好的例子。

4. 動態規劃的演算法

  • 原問題:考慮最大子段和原問題:給定n個數(可以為負數)的序列(a1,a2,...,an),求max{0,max1ijnk=ijak}
  • 子問題界定:設前邊界為1,後邊界為i,且C(i)是子序列A[1,..i]必須包含元素A[i]向前連續延伸的最大子段和:C[i]=max1ki{j=kiA[j]}
這裡寫圖片描述
  • 遞推方程滿足:C[i]=max{C[i1]+A[i],A[i]}i=2,...,nC[1]={A[1]ifA[1]>00ifA[1]<0
  • 遍歷所有以i(1in)為後邊界的最大子段和Ci得出最優解