為什麼需要演算法複雜度分析?
首先,這和研究資料結構和演算法的目的有關——“快”而“省”的解決問題。那麼如何衡量演算法的效能呢?就需要演算法複雜度分析。
其次,除了演算法複雜度分析,還有一種方法可以衡量複雜度,那就是“事後統計法”,即直接執行程式,統計需要的時間和空間。但是,這種方法有兩個問題:
1)結果非常依賴於測試環境。比如,用 Core i3 和用 Core i8 執行程式所需的時間是不同的;
2)結果受測試規模的影響特別大。比如,對有序陣列進行排序的時間比對逆序陣列排序的時間短;對於小規模資料而言,插入排序所需時間比快速排序要短。
所以,就需要有一種不用具體測試資料,也能估計演算法執行效率的方法,就是演算法複雜度分析,包括時間、空間複雜度分析。
大 O 複雜度表示法
大 O 複雜度表示法可以歸納成下面一句話:
所有程式碼的執行時間
T(n)
與每行程式碼的執行次數 n 成正比。
即 T(n) = O(f(n))
。其中,f(n)
表示程式碼的執行次數之和。大 O 符號(Big O notation)是用於描述函式漸進行為的數學符號,它代表“order of...”(...階)。
例如,對於程式碼:
function foo (n) {
var i = 0;
var j = 0;
for (; i < n; i++) {
j += i;
}
}
複製程式碼
假設每行程式碼的執行時間一樣,為 unit_time。則第 2、3 行執行時間均為 1 unit_time,第 4、5 行程式碼執行時間均為 n unit_time,整個函式的執行時間 T(n) = (2n + 2) * unit_time
。用大 O 複雜度表示法表示即為 T(n) = O(2n + 2)
。
注意,大 O 時間複雜度並不具體表示程式碼真正的執行時間,而是表示程式碼執行時間隨資料規模增長的變化趨勢。所以,它也叫漸進時間複雜度,簡稱時間複雜度。
當 n 很大時,不左右增長趨勢的低階、常量、係數均可忽略。所以,上面例子中的時間複雜度為 O(n)
。
時間複雜度分析
時間複雜度分析有下面幾個原則:
1)只關注迴圈執行次數最多的一段程式碼;
2)加法原則:總複雜度等於量級最大的那段程式碼的複雜度。用公式表示即為:T1(n) = O(f(m)),T2(n) = O(g(n)),T1(n) + T2(m) = O(max(f(n), g(m)))
;
3)乘法原則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘機。用公式表示即為:T1(n) = O(f(m)),T2(n) = O(g(n)),T1(n) * T2(m) = O(f(n) * g(m))
常見的時間複雜度有以下幾種:
1)常量階:O(1)
2)對數階:O(logn)
3)線性階:O(n)
4)線性對數階:O(nlogn)
5)平方階:O(n ^ 2)
6)指數階:O(2 ^ n)
7)階乘階:O(n!)
其中,1)-5)為多項式量級;6)、7)為非多項式量級,所對應的演算法問題被稱為非確定多項式問題(NP 問題,Non-Deterministic Polynomial)。
空間複雜度分析
空間複雜度全稱為漸進空間複雜度(asymptostic space complexity)。空間複雜度較為簡單,常見的空間複雜度為 O(1)
,O(n)
和 O(n ^ 2)
。
最後,用幾種複雜度的座標圖作為結尾:
本文是《資料結構與演算法之美》的讀書筆記
首發於微信公眾號《程式碼寫完了》