【資料結構&演算法】02-複雜度分析之執行效率和資源消耗
前言
本筆記主要記錄如何分析、統計演算法的執行效率和資源消耗。
必須學會分析複雜度分析。
李柱明部落格:https://www.cnblogs.com/lizhuming/p/15487271.html
複雜度
複雜度分為:
-
時間複雜度。關聯到執行效率。
- 時間複雜度的全稱是 漸進時間複雜度
- 時間複雜度的全稱是 漸進時間複雜度
-
空間複雜度。關聯到資源消耗。
- 空間複雜度全稱就是漸進空間複雜度,表示演算法的儲存空間與資料規模之間的增長關係。
分析方法
大 O 複雜度表示法
先說結論:
- 大 O 複雜度表示方法只是表示一種變化趨勢。
- 忽略掉公式中的常量、低階、係數,只需要記錄一個最大階的量級就可以了。
- 多、加法和乘法規則。
例子-評估累加和的各種演算法執行效率
演算法 1(for 迴圈):
int cal(int n) { int sum = 0; int i = 1; for (; i <= n; ++i) { sum = sum + i; } return sum; }
-
從 CPU 角度看:
- 重複類似的操作:讀資料-運算-寫資料。
- 假設每行程式碼執行事件都為 unit_time。(粗略估計)
- 程式碼中執行的時間為:
T(n) = (2+3n)*unit_time
。
-
結論:所有程式碼的執行時間 T(n) 與每行程式碼的執行次數成正比。
演算法 2(巢狀 for 迴圈):
int cal(int n) { int sum = 0; int i = 1; int j = 1; for (; i <= n; ++i) { j = 1; for (; j <= n; ++j) { sum = sum + i * j; } } }
- 程式碼中執行時間為:
T(n) = (3+3n+3n²)*unit_time=3(n²+n+1)*unit_time
。 - 所有程式碼的執行時間 T(n) 與每行程式碼執行的次數成正比。
大 O 表示
T(n) = O(f(n))
-
上面演算法 1 中的大 O 表示法為:
T(n) = O(2+3n)
。 -
上面演算法 2 中的大 O 表示法為:
T(n) = O(3(n²+n+1))
。 -
大 O 表示法不是表示程式碼真正的執行時間,而是表示程式碼執行時間隨資料規模增長的變化趨勢。
- 即是時間複雜度。
當 n 很大時,公式中的低階、常量、係數三部分並不左右增長趨勢,所以都可以忽略。
只需要記錄一個最大量級就可以了,如果用大 O 表示法表示剛講的那兩段程式碼的時間複雜度,可記為:
T(n) = O(n)
;T(n) = O(n²)
。
時間複雜度分析
當了解了大 O 表示法後,就可以用來分析時間複雜度了。
三個實用的方法:
- 只關注迴圈執行次數最多的一段程式碼。(多)
- 加法法則:總複雜度等於量級最大的那段程式碼的複雜度。(大)
- 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積。(巢狀:積)
關注執行最多的一段程式碼
以上面演算法 1 為例:
-
前面兩行程式碼為常量級別,忽略。
-
3n
中的係數也可忽略。 -
結論:時間複雜度為
O(n)
演算法 2 的時間複雜度就是 O(n²)
加法規則
加法法則:總複雜度等於量級最大的那段程式碼的複雜度。
- 公式:
T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n)))
。
若上面演算法 1 和演算法 2 出現在同一個程式碼段中是,其時間複雜度之和為 O(n)+O(n²)
。
總的時間複雜度就是取最大量級: O(n²)
。
乘法規則
乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積。
- 公式:
T1(n)=O(f(n))
,T2(n)=O(g(n))
;那麼T(n)=T1(n)T2(n)=O(f(n))O(g(n))=O(f(n)*g(n))
。
例子:
- 時間複雜度:
T(n) = T1(n) * T2(n) = O(n*n) = O(n²)
。
int func(int n)
{
int sum = 0;
int i = 1;
for (; i < n; ++i)
{
sum = sum + i;
}
return sum;
}
int cal(int n)
{
int ret = 0;
int i = 1;
for (; i < n; ++i)
{
ret = ret + func(i);
}
}
常見時間複雜度
常見時間複雜度量級如圖:
這些複雜度量級可分為:
-
多項式量級:
- 常量階:
O(1)
- 對數階:
O(logn)
- 線性階:
O(n)
- 線性對數階:
O(nlogn)
- k 次方階:
O(nk)
(注意:這裡的 k 為 k 次方)
- 常量階:
-
非多項式量級
- O(2n);(注意:這裡的 n 為 n 次方)
O(n!)
。- 說明:當資料規模 n 越來越大時,非多項式量級演算法的執行時間會急劇增加,求解問題的執行時間會無限增長。所以,非多項式時間複雜度的演算法其實是非常低效的演算法。
常量階 O(1)
O(1) 只是常量級時間複雜度的一種表示方法,並不是指只執行了一行程式碼。
大牛總結:(常量級記作 O(1)
)
- 只要程式碼的執行時間不隨 n 的增大而增長,這樣程式碼的時間複雜度我們都記作
O(1)
。 - 或者說, 一般情況下,只要演算法中不存在迴圈語句、遞迴語句,即使有成千上萬行的程式碼,其時間複雜度也是
Ο(1)
。
對數階 O(logn)、O(nlogn)
i=1;
while (i <= n)
{
i = i * 2;
}
時間複雜度分析過程:
-
多:第 4 行程式碼執行次數最多。那就算出第四行執行的次數。
-
得 x = log2n 即時間複雜度為 O(log2n)。也就是
O(logn)
。 -
不管底數為何值,都把這類對數階的時間複雜度記為
O(logn)
。理由:- log3n = log32 * log2n。對應時間複雜度為:O(log3n) = O(C * log2n)。
- 按前面學的係數可忽略:O(log3n) = O(log2n)。
- 既然不同底數都可以轉化,那就直接使用
O(logn)
來標記對數階。
而對於 O(nlogn)
就是一段時間複雜度為 O(logn)
的程式碼段被執行了 n 次。
多引數階 O(m+n)、O(m*n)
分析程式碼的時間複雜度由兩個以上資料的規模來決定。
以下以兩個資料規模決定為基礎。
int cal(int m, int n)
{
int sum_1 = 0;
int i = 1;
for (; i < m; ++i)
{
sum_1 = sum_1 + i;
}
int sum_2 = 0;
int j = 1;
for (; j < n; ++j)
{
sum_2 = sum_2 + j;
}
return sum_1 + sum_2;
}
- 其時間複雜度為:
O(m+n)
- 對於加法規則(變了):T1(m) + T2(n) = O(f(m) + g(n))。
- 對於乘法規則(不變):T1(m)*T2(n) = O(f(m) * f(n))。
空間複雜度分析
空間複雜度。關聯到資源消耗。
- 空間複雜度全稱就是漸進空間複雜度,表示演算法的儲存空間與資料規模之間的增長關係。
使用大 O 表示法,和時間複雜度一樣,只是分析的資料規模 n 由時間度量改為空間度量。
小結
複雜度也叫漸進複雜度,包括時間複雜度和空間複雜度,用來分析演算法執行效率與資料規模之間的增長關係。
通常越高階複雜度的演算法,執行效率越低。
常見的複雜度並不多,從低階到高階有:O(1)、O(logn)、O(n)、O(nlogn)、O(n2 )。