【資料結構與演算法-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、平均情況時間複雜度
還是拿上面的程式碼做例子。
想要計算出它的平均情況時間複雜度,有兩種方法,一種不嚴謹的感官上的方法,一種嚴格的概率上的方法。
- 不嚴謹的感官上的方法
我們知道,想要查詢的資料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存在的話。它存在於陣列中每個位置的概率是否一樣?
很明顯,上述方法沒有考慮概率的問題。
- 概率的方法
現在假設,x出現在陣列中與不出現在陣列中的概率是相等的,都是1/2
.同時假設x如果出現在陣列中,則它存在陣列中的每一個位置的概率都是一樣的1/n
。那麼可以用如下方法計算平均情況時間複雜度。
利用大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)
。所以可以利用下面的方法計算平均情況時間複雜度:
所以
- 平均情況時間複雜度: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