1. 程式人生 > >資料結構與演算法分析-第2章

資料結構與演算法分析-第2章

資料結構與演算法分析-第2章

Table of Contents

1 第2章-演算法分析

1.1 數學基礎

  • 四個定義
    • 如果存在正整數 \(c\) 和 \(n_0\) 使得當 \(N\ge n_0\) 時 \(T(N) \le cf(N)\) , 則記為 \(T(N) = O(f(N))\).上界的定義,大O記法.\(T(N)\) 的增長率小於等於 \(f(N)\) 的增長率.
    • 如果存在正整數 \(c\) 和 \(n_0\) 使得當 \(N\ge n_0\) 時 \(T(N) \ge cg(N)\) , 則記為 \(T(N) = \Omega(f(N))\).下界的定義,\(T(N)\) 的增長率大於等於 \(g(N)\) 的增長率.
    • \(T(N)=\Theta(h(N))\) 當且僅當 \(T(N)=O(h(N))\) 且 \(T(N) = \Omega(h(N))\).\(T(N)\) 的增長率等於 \(h(N)\) 的增長率.
    • 如果 \(T(N)=O(p(N))\) 且 \(T(N)\neq\Theta(p(N))\) ,則 \(T(N)=o(p(N))\) .小o記法. \(T(N)\) 的增長率小於 \(p(N)\) 的增長率.大O包含增長率相同的可能性.
  • 法則1: 如果 \(T_1(N)=O(f(N))\) 且 \(T_2(N) = O(g(N))\) ,那麼
    • \(T_1(N) + T_2(N) = max(O(f(N)), O(g(N)))\)
    • \(T_1(N) * T_2(N) = O(f(N) * g(N))\)
  • 法則2: 如果 \(T(N)\) 是一個k次多項式,則 \(T(N) = \Theta(N^k)\).
  • 法則3: 對於任意常數k, \(\log^kN=O(N)\). 它告訴我們對數增長得非常緩慢.
  • 典型的增長率
函式 名稱
c 常數
logN 對數級
log2N 對數平方根
N 線性級
NlogN  
N2 平方級
N3 立方級
2N 指數級
  • 可以通過計算極限 \(\lim_{n\to\infty}\frac{f(N)}{g(N)}\) 來確定兩個函式 \(f(N)\) 和 \(g(N)\) 的相對增長率.
    • 洛必達法則: 若 \(\lim_{n\to\infty}f(N) = \infty\) 且 \(\lim_{n\to\infty}g(N) = \infty\) ,則 \(\lim_{n\to\infty}\frac{f(N)}{g(N)} = \lim_{n\to\infty}\frac{f'(N)}{g'(N)}\), \(f'(N)\) 和 \(g'(N)\) 分別是 \(f(N)\) 和 \(g(N)\) 的導數.
    • 極限是 \(0\) : 說明 \(f(N)=o(g(N))\)
    • 極限是 \(c\neq0\) : 說明 \(f(N)=\Theta(g(N))\)
    • 極限是 \(\infty\) : 說明 \(g(N) = o(f(N))\)
    • 極限擺動: 二者無關.

1.2 執行時間

  • 一般法則
    • 巢狀的for迴圈內部的一條語句總的執行時間為該語句的執行時間乘以該組所有的for迴圈的大小的乘積.
    • 順序語句將各個語句的執行時間求和即可.
    • 斐波那契函式遞迴求法最壞情況的增長率是 \(\frac{5}{3}^n\)

1.2.1 最大自序列和問題求解

  • 演算法1: 時間複雜度為 \(O(N^3)\)
int MaxSubsequenceSum1(const int A[], int N) {
    int ThisSum, MaxSum, i, j, k;

    /* 總開銷: O(1*N*N*N)=O(3) */
    /* 賦值開銷是O(1) */
    MaxSum = 0;
    /* N次的迴圈 */
    for (i = 0; i < N; i++) {
        /* N-i次迴圈 */
        for(j = i; j < N; j++) {
            ThisSum = 0;
            /* j-i+1次迴圈,假設為N次 */
            for (k = i; k <= j; k++) {
                ThisSum += A[k];
            }
            if (ThisSum > MaxSum) {
                MaxSum = ThisSum;
            }
        }
    }
    return MaxSum;
}
  • 演算法2: 時間複雜度為 \(O(N^2)\)
int MaxSubsequnceSum2(const int A[], int N) {
    int ThisSum, MaxSum, i, j;

    MaxSum = 0;
    /* N次迴圈 */
    for(i = 0; i < N; i++) {
        ThisSum = 0;
        /* N-i次迴圈 */
        for(j = i; j < N; j++) {
            ThisSum += A[j];

            if (ThisSum > MaxSum) {
                MaxSum = ThisSum;
            }
        }
    }
    return MaxSum;
}
  • 演算法3: 時間複雜度為 \(O(NlogN)\)
static int MaxSubsequenceSum3(const int A[], int Left, int Right) {
    int MaxLeftSum, MaxRightSum;
    int MaxLeftBorderSum, MaxRightBorderSum;
    int LeftBorderSum, RightBorderSum;
    int Center, i;

    /* Base Case */
    if (Left == Right) {
        if (A[Left] > 0) {
            return A[Left];
        } else {
            return 0;
        }
    }

    Center = (Left + Right) / 2;
    MaxLeftSum = MaxSubsequenceSum3(A, Left, Right);
    MaxRightSum = MaxSubsequenceSum3(A, Left, Right);

    MaxLeftBorderSum = 0;
    LeftBorderSum = 0;
    for (i = Center; i >= Left; i--) {
        LeftBorderSum += A[i];
        if (LeftBorderSum > MaxLeftBorderSum) {
            MaxLeftBorderSum = LeftBorderSum;
        }
    }

    MaxRightBorderSum = 0;
    RightBorderSum = 0;
    for (i = Center + 1; i <= Right; i++) {
        RightBorderSum += A[i];
        if (RightBorderSum > MaxRightBorderSum) {
            MaxRightBorderSum = RightBorderSum;
        }
    }

    return Max3(MaxLeftSum, MaxRightSum, MaxLeftBorderSum + MaxRightBorderSum);
}

int Max(int first, int second, int third) {
    return first > second ?
        (first > third ? first : third) :
        (second > third ? second : third);
}
  • 演算法4: 時間複雜度為 \(O(N)\)
int MaxSubsequenceSum(const int A[], int N) {
    int ThisSum, MaxSum, j;
    ThisSum = MaxSum = 0;

    for (j = 0; j < N; j++) {
        ThisSum += A[j];

        if (ThisSum > MaxSum) {
            MaxSum = ThisSum;
        } else if (ThisSum < 0) {
            ThisSum = 0;
        }
    }
    return MaxSum;
}

1.2.2 執行時間中的對數

  • 對數最常出現的規律的一般法則:
    • 如果一個演算法用常數時間 \(O(1)\) 將問題的大小削減為其一部分(通常是 \(\frac{1}{2}\) ), 那麼該演算法就是 \(O(\log{N})\) .
    • 另一方面,使用常數時間只是把問題減少一個時間常數(如將問題減少 \(1\) ), 那麼該演算法就是 \(O(N)\) 的.
  • 對分查詢: 時間複雜度為 \(O(N)\)
int BinarySearch(const int A[], int X, int N) {
    int Low, Mid, High;

    Low = 0;
    High = N - 1;

    while (Low <= High) {
        Mid = (Low + High) / 2;
        if (A[Mid] < X) {
            Low = Mid + 1;
        } else if (A[Mid] > X){
            High = Mid - 1;
        } else {
            return Mid;
        }
    }
    return -1;
}
  • 最大公因數的歐幾里得演算法: 時間複雜度為 \(O(\log{N})\)
unsigned int Gcd(unsigned int M, unsigned int N) {
    unsigned int Rem;

    while (N > 0) {
        Rem = M % N;
        M = N;
        N = Rem;
    }
    return M;
}
  • 定理: 如果 \(M > N\) ,則 \(M mod N < \frac{M}{2}\)
  • 冪運算: 時間複雜度為 \(O(\log{N})\)
long int
Pow(long int X, unsigned int N) {
    if (N == 0)
        return 1;
    if (N == 1)
        return X;
    if (IsEven(N))
        return Pow(X * X, N / 2);
    else
        return Pow(X * X, N/2) * X;
}

Date: 2018-11-11 16:45

Author: devinkin

Created: 2018-11-11 日 16:58

Validate