前言
這一篇筆記主要記錄總結了什麼是演算法複雜度?、為什要做演算法複雜度分析?、如何做演算法複雜度分析?、常用的複雜度級別?以及如何掌握複雜度分析?等問題。
演算法複雜度分析是什麼?
資料結構與演算法
解決的是如何更省、更快的儲存和處理資料
的問題。因此就需要一個考量效率和資源消耗
的方法,這就是複雜度分析方法。
複雜度也叫漸進複雜度
,其中包含時間複雜度
和空間複雜度
兩個維度,是用來分析演算法執行時間(或者佔用空間)
與資料規模的增長
關係,可以粗略的表示,越高階複雜度的演算法,執行效率越低。
為什麼要做演算法複雜度分析?
-
統計、監控等
事後統計法
的侷限性:- 測試結果高度依賴測試環境
- 測試結果受資料規模影響很大
-
因此需要一個
不用具體的測試資料
就可以粗略地估計演算法的執行效率
的方法。複雜度分析
不依賴執行環境、成本低、可操作性強。 -
掌握
複雜度分析
,將能寫出效能更優的程式碼。
如何做演算法複雜度分析?
大 O 複雜度表示法
- 所有程式碼的執行時間 T(n) 與每行程式碼的執行次數 n 成正比。
大 O 時間複雜度
實際上並不具體代表程式碼真正的執行時間,而是表示程式碼執行時間隨資料規模增長
的變化趨勢
,叫做漸進時間複雜度
,簡稱時間複雜度
。- 由於公式中的低階、常量、係數對增長趨勢不影響,所以都可以忽略,只需要記錄一個最大的量級。
演算法複雜度分析法則
- 只關注迴圈執行次數最多的一段程式碼
- 加法法則:總複雜度等於量級最大的那段程式碼的複雜度
- 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積
空間複雜度分析
類比時間複雜度,空間複雜度全稱就是漸進空間複雜度
,表示演算法的儲存空間
與資料規模之間
的增長關係
。
常見的空間複雜度是:O(1)、O(n)、O(n^2)
最好、最壞時間複雜度
對有些程式碼,其複雜度有不是固定的,有最好、最壞的情況。如陣列元素查詢,有可能第一次就找到,這個時候演算法時間複雜度是常數階O(1),也有可能需要整個遍歷一遍陣列才能找到,這個時候是最壞的時間複雜度O(n)。
- 最好時間複雜度:在最理想情況下,程式碼執行的時間複雜度。
- 最壞時間複雜度:在最糟糕的情況下,程式碼執行的時間複雜度。
平均情況複雜度
由於最好和最壞情況複雜度對應的都是極端情況下的程式碼複雜度,發生的概率並不大,所以需要引入平均情況複雜度
來更好的表示演算法的時間複雜度。
平均情況複雜度
是將各種情況下時間複雜度發生的概率考慮進去,然後計算整體時間複雜度的期望值,所以平均情況複雜度
也叫做加權平均時間複雜度
或者期望時間複雜度
。
很多時候,只要使用一個複雜度就可以滿足需求了。只有同一塊程式碼在不同情況下,時間複雜度有量級的差距
,才會使用這三種複雜度表示法來區分。
均攤時間複雜度
**適用場景:**對一個資料結構進行一組連續操作中,大部分情況下時間複雜度都很低,只有個別情況下時間複雜度比較高,而且這些操作之間存在前後連貫的時序關係
,這個情況下,就可以將這一組操作放在一起分析,看能否將較高時間複雜度的那次操作平攤
到其他那些時間複雜度較低的操作上。一般在能夠應用均攤時間複雜度
的場景,均攤時間複雜度
就等於最好情況時間複雜度
。
分析方法:攤還分析
。
常用的演算法複雜度級別
-
非多項式量級
- O(2^n) 和 O(n!)
- 當資料規模 n 越大時,非多項式量級的演算法的執行時間會急劇增加。所以非多項式量級演算法是非常低效的演算法,因此要避免使用到。
-
多項式量級
- O(1):程式碼的執行時間不隨 n 的增大而增長,這樣程式碼的時間複雜度記做 O(1),一般情況下,如果演算法中不存在迴圈語句、遞迴語句、不管有多少行程式碼,其時間複雜度都是 O(1)。
- O(logn)、O(nlogn):對數階複雜度。
- O(m+n)、O(m*n):程式碼的複雜度由
兩個資料的規模
來決定。
-
多項式量級演算法時間複雜度隨資料規模的增長變化趨勢曲線
如何掌握複雜度分析
多練習。
課後問題
1. 專案之前都會進行效能測試,再做程式碼的時間複雜度、空間複雜度分析是不是多次一舉?
**解答:**不是多此一舉,漸進時間,空間複雜度分析為我們提供了一個很好的理論分析的方向,成本低,可操作性強,不用具體的測試資料就可以粗略地估計演算法的執行效率,有助於寫出效能高效的程式碼,同時培養起演算法複雜度分析的思維,可以和效能測試相輔相成。
2. 分析以下程式碼的複雜度
// 全域性變數,大小為 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;
}
複製程式碼
解答:
- 最好時間複雜度:當 i 小於 len 時,程式碼不執行 if 語句,演算法時間複雜度是 O(1),
- 最壞時間複雜度:當 i >= len 時,程式碼開始執行 if 語句,裡面有個迴圈操作,所以演算法時間複雜度是 O(n)。
- 平均情況複雜度:進入 if 語句後,陣列擴容 2 倍,陣列每次插入元素的概率都是 1/len + 1,所以根據加權時間複雜度計算公式為 1 * 1/len + 1 + 1 * 1/len + 1 + ... + len *(1/(len+1)) = 1,所以平均情況複雜度是 O(1)。
- 均攤時間複雜度:每次 O(len)前都有 len 次 O(1),存在前後連貫的時序關係,所以可以將高時間複雜度的 O(len) 平攤到之前的 len 次上,所以均攤時間複雜度是 O(1)。
分享個人技術學習記錄和跑步馬拉松訓練比賽、讀書筆記等內容,感興趣的朋友可以關注我的公眾號「青爭哥哥」。