1. 程式人生 > >以最大連續和為例的演算法分析

以最大連續和為例的演算法分析

學習劉汝佳老師的《演算法競賽》總結

最大連續和問題
給出一個長度為n的序列A1,A2,……,An,要求找到1ijn,使得Ai+Ai+1+……+Aj儘量大。
一.使用列舉,程式如下:

tot = 0;
    best = A[1]; //初始最大值
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++) {  //檢查連續子序列A[i],……,A[j]
            int sum = 0;
            for (int k = i; k <= j; k++) {
                sum
+= A[k]; //累加元素和 tot++; } if (sum > best) best = sum; //更新最大值 }

設輸入規模為n時,加法操作的次數為T(n),則

T(n)=i=1nj=inji+1=i=1n(ni+1)(ni+2)2=n(n+1)(n+2)6
用一個記號來表示:T(n)=O(n3)

.下面試著優化一下這個演算法。
設Si=A1+A2+……+Ai,則Ai+Ai+1+……+Aj=Sj-Si1,其直觀含義是”連續子序列之和等於兩個字首和之差“。這樣可以省略最內層的迴圈。

S[0] = 0;
    for (int i = 1; i <= n; i++)
        S[i] = S[i - 1] + A[i];         //遞推字首和S
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++)
            best = max(best, S[j] - S[i - 1]);  //更新最大值

類似的方法可以分析出:

T(n)=i=1nni+1=n(n+1)2
時間複雜度是O(n2).

三.分治法
分治演算法一般分為以下三個步驟:
劃分問題

:把問題的例項劃分為子問題;
遞迴求解:遞迴解決子問題;
合併問題:合併子問題的解得到原問題的解。
在本例中,”劃分“就是把序列儘量分成元素個數相等的兩半;”遞迴求解“就是分別求出完全位於左半和完全位於右半的最佳序列;”合併“就是求出起點位於左半,終點位於右半的最大連續和序列,並和子問題的最優解比較。

int maxsum(int *A, int x, int y) {     //返回陣列在左閉右開區間[x,y)中的最大連續和
    int V, L, R, maxs;
    if (y - x == 1)
        return A[x];
    int m = x + (y - x) / 2;     //分治第一步:劃分成[x,m)和[m,y)
    maxs = max(maxsum(A, x, m), maxsum(A, m, y));    //分治第二步:遞迴求解
    V = 0;
    L = A[m - 1];                 //分治第三步:合併(1)——從分界點開始往左的最大連續和L
    for (int i = m - 1; i >= x; i--)
        L = max(L, V += A[i]);
    V = 0;
    R = A[m];                   //分治第三步:合併(2)——從分界點開始往右的最大連續和R
    for (int i = m; i <= y; i++)
        R = max(R, V += A[i]);
    return max(maxs, L + R);         //把子問題的解與L + R比較
}

遞迴方程T(n)=2T(n/2)+O(n),T(1)=1,解為T(n)=O(n log n).

四.演算法分析結果
表-運算量隨著規模的變化

運算量 最大規模 速度擴大兩倍後
n! 11 11
2n 26 27
n3 464 584
n2 10000 14142
nlog2n 4.5x106 8.6x106
n 100000000 200000000

借鑑此表,在演算法競賽中,一個指明n8的題目可能n!的演算法已經足夠,n20的題目需要用到2n 的演算法,而n300的題目可能必須用至少n3的多項式時間演算法了。