複雜度分析(下):淺析最好、最壞、平均、均攤時間複雜度

1、時間複雜度分析有哪些?

  • 最好情況時間複雜度(best case time complexity)
  • 最壞情況時間複雜度(worst case time complexity)
  • 平均情況時間複雜度(average case time complexity)
  • 均攤時間複雜度(amortized time complexity)

 

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

最好情況時間複雜度就是在最理想的情況下,執行這段代碼的時間複雜度。

最壞情況時間複雜度就是在最糟糕的情況下,執行這段代碼的時間複雜度。

 1 // n表示數組array的長度
 2 int find(int[] array, int n, int x) {
 3   int i = 0;
 4   int pos = -1;
 5   for (; i < n; ++i) {
 6     if (array[i] == x) {
 7        pos = i;
 8        break;
 9     }
10   }
11   return pos;
12 }

上面這段代碼要實現的功能是:在一個數組中查找變量 x 出現的位置,如果找到了,就馬上跳出循環,返回它的位置值;如果找不到,就返回 -1。

這裏不能只是看到了 for 循環就判定其時間複雜度爲 O(n),因爲這個數組的順序是不確定的,有可能數組中的第一個元素就是 x,那就可以馬上結束循環了,其時間複雜度就是 O(1);如果數組中不存在變量 x 或者是數組中的最後一個元素纔是 x,那就需要遍歷整個數組,時間複雜度就是 O(n)。

在這裏,O(1) 就是最好情況時間複雜度,O(n) 就是最壞情況時間複雜度。

 

3、平均情況時間複雜度

最好、最壞情況時間複雜度都是極端情況下的代碼複雜度,發生的概率很小。因此,我們還需要知道平均情況時間複雜度。

還是以剛剛查找變量 x 的位置的例子爲例,要查找的變量 x 在數組中的位置,總共有 n+1 種情況:在數組的 0~n-1 位置中和不在數組中。我們把每種情況下,查找需要遍歷的元素個數累加起來再除以 n+1,就可以得到需要遍歷的元素個數的平均值,即:

 省略掉係數、低階、常量,將以上公式進行簡化之後,得到的平均時間複雜度就是 O(n)。

但是,以上的 n+1 種情況,沒有將各種情況發生的概率考慮進去。這裏可以引入概率論的相關知識,假設變量 x 在數組中與不在數組中的概率各爲 1/2,出現在 0~n-1 這 n 個位置的概率都是 1/n。根據概率乘法法則,要查找的數據出現正在 0~n-1 中任意位置的概率就是 1/(2n)。

這樣,就可以得到以下計算過程:

 這個值就是概率論中的加權平均值,也叫做期望值。根據這個加權平均值,去掉係數和常量,我們得到的平均時間複雜度也是 O(n)。所以,平均時間複雜度就是:加權平均時間複雜度(亦稱爲期望時間複雜度)。

大部分情況下,我們並不需要區分最好、最壞、平均三種複雜度,平均複雜度只在某些特殊情況下纔用到。

 

4、均攤時間複雜度

均攤時間複雜度:對一個數據結構進行一組連續操作中,大部分情況下時間複雜度都很低,只有個別情況下時間複雜度較高。而且這些操作之間存在前後連貫的時序關係,在這個時候,我們可以將這一組操作放在一塊兒分析,看是否能將較高時間複雜度那次操作的耗時,平攤到其他那些時間複雜度較低的操作上

均攤時間複雜度的應用場景比平均時間複雜度更加特殊、更加有限。

 1  // array表示一個長度爲n的數組
 2  // 代碼中的array.length就等於n
 3  int[] array = new int[n];
 4  int count = 0;
 5  
 6  void insert(int val) {
 7     if (count == array.length) {
 8        int sum = 0;
 9        for (int i = 0; i < array.length; ++i) {
10           sum = sum + array[i];
11        }
12        array[0] = sum;
13        count = 1;
14     }
15 
16     array[count] = val;
17     ++count;
18  }

這段代碼實現的是往一個數組中插入數據的功能,當數組滿了以後,也就是 count == array.length 的時候,我們用 for 循環遍歷數組求和,再將新的數據插入,其時間複雜度爲 O(n)。但如果數組未滿,則直接將數據插入數組,其時間複雜度爲 O(1)。

個人體會: 平均和均攤基本算是一個概念,均攤是特殊的平均。出現O(1)的次數遠大於出現O(n)出現的次數,那麼平均和均攤時間複雜度就是O(1)。

我們來分析一下它的時間複雜度,數組的長度爲 n,根據數據插入的不同位置,可以分爲 n 種情況,每種情況的時間複雜度爲 O(1)。還有一種最“糟糕”的情況,那就是數組已滿,這個時候的時間複雜度爲 O(n)。而且,這 n+1 種情況發生的概率是一樣的,都是 1/(n+1)。所以,根據加權平均的計算方法,可知:

 對比一下 insert() 的例子和前面 find() 的例子,這兩個例子的最好、最壞情況時間複雜度都一樣,爲什麼平均時間複雜度相差這麼多呢?

這兩個例子之間最大的區別在於:find() 的最好、最壞情況時間複雜度都是在極端情況下才會發生,而 insert() 在大部分情況下,時間複雜度都是 O(1),只有在極端情況下,時間複雜度纔是 O(n)。其次,對於 insert() 函數來說,每當碰到一個時間複雜度爲 O(n) 的情況,接下來就會有 n-1 個 O(1) 的插入操作,循環往復。

均攤下來,這一組連續的操作的均攤時間複雜度就是 O(1)。這就是均攤時間複雜度分析的大致思路。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章