演算法複雜度分析(下)

dannab發表於2018-09-30

前一篇文章演算法複雜度分析(上)講述了複雜度的大 O 表示法和幾個分析原則,這篇文章我們來講講另外幾種複雜度,最好情況時間複雜度(best case time complexity)、最壞情況時間複雜度(worst case time complexity)、平均時間複雜度(average case time complexity)和均攤時間複雜度(amortized time complexity)。

最好、最壞情況時間複雜度

顧名思義,這兩種時間複雜度指的是特殊情況下的時間複雜度。我們看下面的例子:

// n 表示陣列 array 的長度
int find(int[] array, int n, int x) {
  int i = 0;
  int pos = -1;
  for (; i < n; ++i) {
    if (array[i] == x) {
        pos = i;
        break;
    }
  }
  return pos;
}
複製程式碼

這段程式碼實現的功能是,在陣列 array 中尋找變數 x 第一次出現的位置,若沒有找到,則返回 -1;否則返回位置下標。

用上一篇文章的方法顯然是無法分析這段程式碼的複雜度的。因為,不同情況下的時間複雜度是不同的。當陣列中第一個元素就是要找的 x 時,時間複雜度是 O(1);而當最後一個元素才是 x 時,時間複雜度則是 O(n)。

為了表示程式碼在不同情況下的時間複雜度,就需要引入三個概念:最好情況時間複雜度、最壞情況時間複雜度和平均情況時間複雜度。

其中,最好情況時間複雜度就是在最理想情況下執行程式碼的時間複雜度,它的時間是最短的;最壞情況時間複雜度就是在最糟糕情況下執行程式碼的時間複雜度,它的時間是最長的。

平均情況時間複雜度

最好、最壞時間複雜度反應的是極端條件下的複雜度,發生的概率不大,不能代表平均水平。那麼為了更好的表示平均情況下的演算法複雜度,就需要引入平均時間複雜度。

繼續用前面 find 函式為例,假設變數 x 在和不在陣列 array 中的概率分別為 1 / 2;當存在於陣列中時,在每個位置的概率均等,為 1 / n。那麼,平均情況時間複雜度就可以用下面的方式計算:

((1 + 2 + ... + n) / n + n) / 2 = (3n + 1) / 4

這個值就是概率論中的加權平均值,也叫期望值。所以平均情況時間複雜度也叫加權平均時間複雜度或期望時間複雜度。可見,find 函式的平均時間複雜度為 O(n)。

大多數情況下,不需要區分最好、最壞、平均情況時間複雜度,只用一個複雜度就可以滿足需求了。只有當同一塊程式碼在不同情況下,時間複雜度有數量級上的區別時,才需要考慮這三種複雜度。

均攤時間複雜度

由上面我們可以知道,平均時間複雜度只有在某些特殊的時候才會用到。均攤時間複雜度的應用場景比它更為特殊。均攤時間複雜度是指,當大部分情況下時間複雜度都很低,只有個別情況下時間複雜度比較高時,並且這些操作之間存在著前後連貫的時序關係,這時候,可以將較高時間複雜度的操作耗時均攤至時間複雜度較低的操作上。這種分析方法叫做攤還分析法,得到的複雜度叫做均攤時間複雜度。

而且,在能夠應用均攤時間複雜度分析的場合,一般均攤時間複雜度就等於最好情況時間複雜度。

例如:

int[] array = new int(n);
int count = 0;

void addLast (int val) {
    if (count == array.length) {
        int[] newArray = new int(2 * n);
        for (int i = 0; i < 2 * n; i++) {
            newArray[i] = array[i];
        }
        newArray[count] = val;
        array = newArray;
    } else {
        array[count] = val
    }
    count++;
}
複製程式碼

這段程式碼實現的功能是往陣列的末尾增加一個元素,如果陣列沒有滿,直接往後面插入元素;如果陣列滿了,即 count == array.length,則將陣列擴容一倍,然後再插入元素。

例如,陣列長度為 n,則前 n 次呼叫 addLast() 複雜度都為 O(1);第 n + 1 次則需要先進行 n 次元素轉移操作,然後再進行 1 次插入操作,複雜度為 O(n)。而且很容易看出,O(1) 複雜度的操作和 O(n) 複雜度的操作出現頻率是有規律的,每 n 次 O(1) 操作後會跟隨一個 O(n) 操作。

那麼,就可以將 O(n) 操作的複雜度均攤至每次 O(1) 操作中,均攤下來,這組操作的需要進行 (n + n * 1) / (n + 1) = 2n / (n + 1) 次操作,所以均攤複雜度為 O(1)。


本文首發自微信公眾號《程式碼寫完了》

演算法複雜度分析(下)

相關文章