1. 程式人生 > >【資料結構與演算法-java實現】二 複雜度分析(下):最好、最壞、平均、均攤時間複雜度的概念

【資料結構與演算法-java實現】二 複雜度分析(下):最好、最壞、平均、均攤時間複雜度的概念

上一篇文章學習了:如何分析、統計演算法的執行效率和資源消耗? 點選連結檢視上一篇文章:複雜度分析上

今天的文章學習以下內容:

  • 最好情況時間複雜度
  • 最壞情況時間複雜度
  • 平均情況時間複雜度
  • 均攤時間複雜度

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

我們首先來看一段程式碼,利用上一篇文章學習的知識看看能否分析出它的時間複雜度。

// 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; }

這段程式碼很簡單:就是在一個數組中找到一個數X的下標,將其返回。

利用上一篇文章學習的大O表示法來分析時間複雜度的話,有一些問題。if迴圈中有一個breadk語句,當條件成立,得到下標值立馬退出迴圈。那麼時間複雜度,就不能籠統的說是O(n)。

因為當想要查詢的數就在第一個位置時,時間複雜度實際上是O(1),當想要查詢的數在最後一個位置的時候,時間複雜度是O(n)。那麼這個時候,我們就需要引入幾個新名詞:最好情況時間複雜度,最壞情況時間複雜度。

很容易理解。

  • 最好情況時間複雜度就是在最理想的情況下,執行程式碼需要的時間複雜度。就像上述程式碼,如果想要查詢的數就是陣列中的第一個位置,那麼時間複雜度就是O(1),此時就是最好情況時間複雜度。
  • 最壞情況時間複雜度是在最不理想的情況下,執行程式碼需要的時間複雜度。還是如上述程式碼,入股我們想要查詢的數在陣列的最後一個位置,那麼時間複雜度就是O(n),此時就是最壞情況時間複雜度。

其實還有一種情況,就是我們想要查詢的數不在第一個位置也不在最後一個位置的時候。此時的時間複雜度是多少呢?這種情況下叫做平均情況時間複雜度

那麼平均情況時間複雜度如何計算?它是多少呢?

2、平均情況時間複雜度

還是拿上面的程式碼做例子。

想要計算出它的平均情況時間複雜度,有兩種方法,一種不嚴謹的感官上的方法,一種嚴格的概率上的方法。

  1. 不嚴謹的感官上的方法

我們知道,想要查詢的資料x有兩種情況的存在,一種是存在陣列的0~n-1的某一個位置(n種可能),一種是不在這個陣列中(1中可能,就是不在陣列中)。這兩種情況一共有n+1種可能。對於在陣列中的n中可能中,每一種查詢的次數分別是1,2,3,4…n。對於不在陣列中的這種可能,查詢次數是n。所以可以這麼計算平均情況時間複雜度:

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

上一篇文章我們知道,利用大O表示法,,可以將計算結果的係數,低階,常量去掉。那麼上述的結果就是O(n)。

為什麼說他不嚴謹呢?考慮以下情況。要找的數x存在於陣列中與不存在於陣列中的概率是否一樣?x存在的話。它存在於陣列中每個位置的概率是否一樣?

很明顯,上述方法沒有考慮概率的問題。

  1. 概率的方法

現在假設,x出現在陣列中與不出現在陣列中的概率是相等的,都是1/2.同時假設x如果出現在陣列中,則它存在陣列中的每一個位置的概率都是一樣的1/n。那麼可以用如下方法計算平均情況時間複雜度。

1 × 1 n × 1 2 + 2 × 1 n × 1 2 + 3 × 1 n × 1 2 + . . . + n × 1 n × 1 2 + n × 1 2 = 3 n + 1 4 (1 \times \frac{1}{n} \times \frac{1}{2}+2 \times \frac{1}{n} \times \frac{1}{2} +3 \times \frac{1}{n} \times \frac{1}{2} +...+n \times \frac{1}{n} \times \frac{1}{2} + n \times \frac{1}{2})= \frac{3n+1}{4}

利用大O表示法來表示的話,依然是O(n)。這就是上面那段程式碼的平均情況時間複雜度。

一般情況下,我們只是用其中一種複雜度來分析問題就夠了,不需要費力去求解三種時間複雜度。只有在時間複雜度有量級的差距時,才會在不同的情況下使用不同的時間複雜度。

3、均攤時間複雜度

上面學會了最好最壞與平均時間複雜度。還有一種時間複雜度叫做均攤時間複雜度。 為了理解均攤時間複雜度,我們先來看一個程式碼:

 // array 表示一個長度為 n 的陣列
 // 程式碼中的 array.length 就等於 n
 int[] array = new int[n];
 int count = 0;
 
 void insert(int val) {
    if (count == array.length) {
       int sum = 0;
       for (int i = 0; i < array.length; ++i) {
          sum = sum + array[i];
       }
       array[0] = sum;
       count = 1;
    }

    array[count] = val;
    ++count;
 }

上述程式碼的意思是:往陣列中插入資料。當陣列沒有滿的時候,直接在最後插入,當陣列滿的時候,先把陣列的所有元素求和,然後清空陣列,將求得的和放到陣列的第一個位置,然後將要插入的數插到第二個位置(聰明的人已經發現它其實就是一個求輸入流中所有數字的和,至於清空陣列,這個只是將下標count從末尾挪到第二個位置,就可以認為是清空陣列)。

利用上述的分析,我們可以求得:

  • 最好情況時間複雜度:O(1),因為陣列不滿的時候直接插入,不用計算和。
  • 最壞情況時間複雜度:O(n),因為此時陣列滿了,需要遍歷一遍陣列然後求和。

平均時間複雜度,還可以利用上述的概率分析法來計算。假設陣列長度是n,往陣列插入資料會有兩種情況發生。陣列沒滿時,插入的時間複雜度是O(1),且插入的位置有n種可能。陣列滿時,插入的時間複雜度是O(n),插入的位置只有一種可能。所以一共有n+1種可能插入的位置,且插入到它們位置的概率是一樣的都是1/(n+1)。所以可以利用下面的方法計算平均情況時間複雜度:
1 × 1 n + 1 + 2 × 1 n + 1 + 3 × 1 n + 1 + . . . + n × 1 n + 1 + n × 1 n + 1 = O 1 (1 \times \frac{1}{n+1}+2 \times \frac{1}{n+1} +3 \times \frac{1}{n+1} +...+n \times \frac{1}{n+1} + n \times \frac{1}{n+1})= O(1)

所以

  • 平均情況時間複雜度:O(1)

上述計算平均時間複雜度的方法,不管怎麼樣,還是有一些複雜。畢竟我們不是研究數學的。所以還是希望儘可能簡單。

針對上面的兩個程式碼例子,一個是find函式,一個是insert函式。看看他們有什麼不同。find函式是最極端的情況下時間複雜度是O(1),大多數的情況下時間複雜度是O(n),而insert函式是大多數情況下的時間複雜度是O(1),只有極端的情況下時間複雜度是O(n)。

而且,對於insert函式,它的O(1)出現的是連續出現多次,然後出現一次O(n)時間複雜度。這具有一種時序關係。

針對這種情況,給出一種特殊的時間複雜度分析方法,均攤時間複雜度。可以通過攤還分析法,的帶均攤時間複雜度。那麼針對insert函式,如何通過攤還分析法來得到均攤時間複雜度呢?

首先,對於insert函式,大多是出現O(1)時間複雜度的,一共出現n次O(1)時間複雜度後,才出現一次O(n)時間複雜度。雖然O(n)時間複雜度消耗的時間比較多,但是O(1)時間複雜度出現的次數多,我們可以將O(n)消耗的時間,均攤給其他n個O(1)時間複雜度操作上的話,對於O(1)時間複雜度,也並不會有多大的影響,就好比,100個人共同擡100斤水泥,而另外又有一個人在擡100斤水泥,如果這個人把水泥平均分給那100人,那100人也才每個人多擡了一斤的水泥,這相比讓那一個人擡100斤水泥,簡直不要太輕鬆!!!。 所以,對於insert函式均攤時間複雜度為O(1)。這等於大多數情況下的時間複雜度。

綜上:

  • 均攤時間複雜度為:O(1)

由以上的分析,我們得出,大概在以下情況下可以使用攤還分析來分析均攤時間複雜度

對一個數據結構進行連續的操作,如果大多數情況下的時間複雜度比較低,只有極端情況下時間複雜度很高,而且這些操作在時序上存在前後連貫的關係。那麼此時,就可以將比較耗時的那個操作,均攤給大多數低的時間複雜度的操作上。

而且,一般可以用均攤時間複雜度分析的情況,均攤時間複雜度就等於最好情況時間複雜度

4、總結

上面學習了四種時間複雜度的分析。但是一般來說,平均時間複雜度用的很少,均攤時間複雜度用的就更少。而且,均攤時間複雜度,實際上是一種特殊的平均時間複雜度。

所以不必糾結到底用什麼複雜度來分析問題,根據實際問題需要實際分析。對於一段程式碼,如果它的時間複雜度在不同情況下量級不同,可以採用不同的方法進行對比分析。其中最好最壞時間複雜度比較好分析,平均時間複雜度與均攤時間複雜度比較難分析。

但是對於平均和均攤。他們實際是一個意思,都有平均的意思。當出現O(1)操作的多於O(n)操作的時候,平均和均攤時間複雜度就都是O(1)。 這是一種感覺。一般情況下,我們都可以感覺對,而不用實際的計算。

本文是自己學習極客時間專欄-資料結構與演算法之美后的筆記總結。如有侵權請聯絡我刪除文章。

學習探討加個人(免費送技術書籍PDF電子書):
qq:1126137994
微信:liu1126137994