關注公衆號 MageByte,設置星標點「在看」是咱們創造好文的動力。後臺回覆 「加羣」 進入技術交流羣獲更多技術成長。 本文來自 MageByte-青葉編寫java
上次咱們說過 時間複雜度與空間復度,列舉了一些分析技巧以及一些常見的複雜度分析好比 O(1)、O(logn)、O(n)、O(nlogn),今天會繼續細化時間複雜度。算法
1. 最好狀況時間複雜度(best case time complexity)數組
2.最壞狀況時間複雜度(worst case time complexity)數據結構
3. 平均狀況時間複雜度(average case time complexity)ide
4.均攤時間複雜度(amortized time complexity)函數
public int findGirl(int[] girlArray, int number) { int i = 0; int pos = -1; int n = girlArray.lentgh(); for (; i < n; ++i) { if (girlArray[i] == number) { pos = i; break; } } return pos; }
代碼邏輯你應該很容易看出來,在無序數組 中查找 number 出現的位置,若是沒找到就返回 -1。《唐伯虎點秋香》主角星爺經過這個方法遍歷數組找到秋香,由於此刻咱們尚未學會各類風騷的算法,只能從頭至尾查驗是否是秋香,因此只能遍歷數組。girlArray 數組保存着秋香、冬香、春香……的編碼,如今唐伯虎經過 選擇 number 這個編碼比對是不是秋香。學習
這段代碼在不一樣的狀況下,時間複雜度是不同的,因此爲了描述代碼在不一樣狀況下的不一樣時間複雜度,咱們引入了==最好、最壞、平均時間複雜度==。n = girlArray 數組的長度。編碼
在最理想的狀況下,執行這段代碼的時間,也就是「唐伯虎」最快點中秋香。假如 這一排姑娘就表明 girlArray 數組,number 變量就是秋香的編碼。假如第一個姑娘就是「秋香」那時間複雜度就是 O(1)。3d
在最糟糕的狀況下,執行這段代碼的時間複雜度。也就是要一個個查驗真個數組的長度 O(n)。code
其實最好與最壞狀況是極端狀況,發生的機率並不大。因此爲了更準確的表示平均狀況下的時間複雜度,引入另外一個改變:平均狀況時間複雜度。
仍是上面的「找秋香」代碼,判斷 number 編碼在循環中出現的位置,有 ==n + 1==種狀況:
在數組 0~n-1 中和不在這個數組中。在數組中共有 n 種狀況,加上不在數組中則就是 n + 1 種了。 每種狀況要遍歷的姑娘人數都不一樣。咱們把每種狀況須要查找姑娘的數量累加,而後再除以 全部狀況數量 (n + 1),就獲得須要遍歷次數的平均值。敲黑板了:公式就是平均狀況複雜度 = 累加每種遍歷的元素個數 / 全部的狀況數量
平均狀況複雜度爲:
$$\frac {((1+2+3… +n) + n)} {(n+1)} = \frac {n(n+3)} {2(n+1)}$$
推導過程:
$$\because 1+2+3 …+ n = n + (n-1) + (n-2)… + 1$$
$$\therefore (1 +2 +3… + n) = \frac {n(1+n)} {2}$$
$$\therefore (1+2+3+…+n) + n = \frac {n(3+n)} {2}$$
根據咱們以前學的 時間複雜度與空間復度 大 O 表示法,省略係數、地接、常量,因此平均狀況時間複雜度是 O(n)。
上面的平均狀況時間複雜度推導沒有考慮每種狀況的發生機率,這裏的 n+1 種狀況,每種狀況發生的機率是不同的,因此還要引入各自發生的機率再具體分析。
秋香的編號 number 要麼在 0 ~ n-1 中,要麼不在 0~n-1 中,因此他們的機率是 $\frac {1} {2}$。
同時 number 在 0~n-1 各個位置的機率是同樣的爲 1/n。根據機率乘法法則,number 在 0~n-1 中任意位置的機率是 $$\frac {1} {2n}$$。
因此在前面推導的基礎上,咱們再把每種狀況發生的機率考慮進去,那麼平均狀況時間複雜度的計算過程就是:
考慮機率的平均狀況複雜度:
$$(1 \frac {1} {2n} + 2 \frac {1} {2n}+ 3 \frac {1} {2n}…+n\frac {n} {2n} ) + n \frac {1} {2} = \frac {3n+1} {4}$$
這就是機率論中的加權平均值,也叫作指望值,因此平均時間複雜度全稱叫:加權平均時間複雜度或者指望時間複雜度。
引入機率以後,平均複雜度變爲 O($$\frac {3n+1} {4}$$),忽略係數以及常量,最後獲得的加權平均時間複雜度爲 O(n)。終於分析推導完了,同窗們能夠鬆一口氣。
注意:
多數狀況下,咱們不須要區分最好、最壞、平均狀況時間複雜度。只有同一塊代碼在不一樣狀況下時間複雜度有量級差距,咱們纔會區分 3 種狀況,爲的是更有效的描述代碼的時間複雜度。
最後一個硬骨頭來了,瞭解了上面加上機率的指望時間複雜度再看這個就容易多了。均攤時間複雜度,聽起來跟平均時間複雜度有點兒像。
均攤複雜度是一個更加高級的概念,它是一種特殊的狀況,應用的場景也更加特殊和有限。
對應的分析方式稱爲:攤還分析或平攤分析。
// array 表示一個長度爲 n 的數組 // 代碼中的 array.length 就等於 n int[] array = new int[n]; int count = 0; public 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 == array.lenth,遍歷數組求和,將求和以後的 sum 值放到數組的第一個位置,而後再將新的數據插入。但若是數組一開始就有空閒空間,則直接將數據插入數組。這裏的數據滿:對於可反覆讀寫的存儲空間,使用者認爲它是空的它就是空的。若是你定義清空是所有重寫爲 0 或者某個值,那也能夠!使用者只關心要存的新值!
分析上述的時間複雜度:
平均時間複雜度
數組長度爲 n,由於能夠插入不一樣位置,因此有 n 種狀況,每種複雜度爲 O(1)。
還有一種特殊狀況,沒有空閒空間插入的時候,複雜度是 O(n),一共就是 n+1 種狀況,且每種狀況的機率都是 $$\frac{1} {n+1}$$。因此根據加權平均計算法,平均時間複雜度:
$$(1 \frac {1} {n+1} + 1 \frac {1} {n+1}+ 1 \frac {1} {n+1}…+1\frac {1} {n+1} ) + n \frac {1} {n+1} = \frac {2n} {n+1}$$
當省略係數及常量後,平均時間複雜度爲 O(1)。
其實咱們不須要這麼複雜,對比 findGirl 跟 insert 方法。
攤還分析法
分析上述示例的平均複雜度分析並不須要如此複雜,無需引入機率論的知識。
由於經過分析能夠看出,上述示例代碼複雜度大多數爲 O(1),極端狀況下複雜度才較高爲 O(n)。同時複雜度遵循必定的規律,通常爲 1 個 O(n),和 n 個 O(1)。針對這樣一種特殊場景使用更簡單的分析方法:攤還分析法。
經過攤還分析法獲得的時間複雜度爲均攤時間複雜度。
大體思路:每一次 O(n)都會跟着 n 次 O(1),因此把耗時多的複雜度均攤到耗時低的複雜度。獲得的均攤時間複雜度爲 O(1)。
應用場景:均攤時間複雜度和攤還分析應用場景較爲特殊,對一個數據進行連續操做,大部分狀況下時間複雜度都很低,只有個別狀況下時間複雜度較高。而這組操做其存在先後連貫的時序關係。
這個時候咱們將這一組操做放在一塊兒分析,將高複雜度均攤到其他低複雜度上,因此通常均攤時間複雜度就等於最好狀況時間複雜度。
注意: 均攤時間複雜度是一種特殊的平均複雜度(特殊應用場景下使用),掌握分析方式便可。
均攤時間複雜度就是一種特殊的平均時間複雜度,咱們不必花太多精力去區分它們。你最應該掌握的是它的分析方法,攤還分析。至於分析出來的結果是叫平均仍是叫均攤,這只是個說法,並不重要。
最後留一個問題給你們,用本文學習的只是分析下面代碼的「最好」、「最壞」、「均攤」時間複雜度。
/ 全局變量,大小爲 10 的數組 array,長度 len,下標 i。 int array[] = new int[10]; int len = 10; int i = 0; // 往數組中添加一個元素 void add(int element) { if (i >= len) { // 數組空間不夠了 // 從新申請一個 2 倍大小的數組空間 int new_array[] = new int[len*2]; // 把原來 array 數組中的數據依次 copy 到 new_array for (int j = 0; j < len; ++j) { new_array[j] = array[j]; } // new_array 複製給 array,array 如今大小就是 2 倍 len 了 array = new_array; len = 2 * len; } // 將 element 放到下標爲 i 的位置,下標 i 加一 array[i] = element; ++i; }
整體的含義就是向數組添加一個元素,當空間不夠的時候從新生情一個原來兩倍空間的數組並把原來的數組數據依次複製到新數組中。
其實同窗們這裏還能夠拓展到 HashMap 的拓容,當元素大刀負載因子 0.75 的容量,HashMap 須要拓容爲原來的兩倍而後再從新 把元素放到新數組中。那麼時間複雜度又是多少呢?
關注公衆號 MageByte 後臺回覆 「add」獲取本題目答案,也能夠回覆「加羣」加入技術羣跟咱們一塊兒分享你的見解,咱們第一是時間反饋。
參考文獻:《數據結構與算法之美》