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


時間復雜度分析有哪些?

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

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

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

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

來看看下面這段代碼:

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) 就是最壞情況時間復雜度。

平均情況時間復雜度

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

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

1 + 2 + 3 + …… + n + n / n + 1 = n(n+1)+2n / 2(n+1) = n(n+3) / 2(n+1)

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

但是,以上的 n+1 種情況,出現的概率也不是一樣的。這里可以引入概率論的相關知識,假設變量 x 在數組中與不在數組中的概率各為 1/2,出現在 0~n-1 這 n 個位置的概率都是 1/n。根據概率乘法法則,要查找的數據出現正在 0~n-1 中任意位置的概率就是 1/(2n)。

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

1 x 1/2n + 2 x 1/2n + 3 x 1/2n + …… + n x 1/2n + n x 1/2n = n(n+1)+2n / 2 x 1/2n = 3n+1 / 4

這個值就是概率論中的加權平均值,也叫做期望值。

根據這個加權平均值,去掉系數和常量,我們得到的平均時間復雜度也是 O(n)。

所以,平均時間復雜度就是:加權平均時間復雜度(亦稱為期望時間復雜度)。

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

均攤時間復雜度

均攤時間復雜度:對一個數據結構進行一組連續操作中,大部分情況下時間復雜度都很低,只有個別情況下時間復雜度較高。而且這些操作之間存在前后連貫的時序關系,在這個時候,我們可以將這一組操作放在一塊兒分析,看是否能將較高時間復雜度那次操作的耗時,平攤到其他那些時間復雜度較低的操作上。(在能夠應用均攤時間復雜度分析的場合,一般均攤時間復雜度就等於最好情況時間復雜度)

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

舉個例子:

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)。

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

1 x 1/n+1 + 1 x 1/n+1 + …… + 1 x 1/n+1 + n x 1/n+1 = 1

我們求得的平均時間復雜度就是:O(1)。

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

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

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

內容小結

最好、最壞情況時間復雜度分析起來比較簡單,但平均、均攤時間復雜度分析相對比較復雜。

之所以分為這四種復雜度分析,是因為在不同輸入的情況下,復雜度量級有可能是不一樣的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM