資料結構與算法系列課程之二:複雜度分析(上)
資料結構和演算法,本身就是要解決 “快” 和 “省” 的問題。考量的指標分別就是 “時間複雜度” 和 “空間複雜度”。
時間複雜度表示程式碼執行時間隨著資料規模增長的變化趨勢,也叫漸進時間複雜度。
空間複雜度,全稱漸進空間複雜度,表示演算法的儲存空間和資料規模之間的增長關係。
進行復雜度分析的原因:
事後統計法:簡單來說,就是讓程式碼在實際的平臺跑一遍。
事後統計法有以下侷限性:
1,測試結果非常依賴測試環境
2,測試結果受資料規模的影響很大
大O複雜度表示法:
T(n) = O(f(n))
T(n)表示程式碼的執行時間,n表示資料規模的大小,f(n)表示每行程式碼執行的次數總和
時間複雜度分析:
1,只關注迴圈執行次數最多的一段程式碼
程式碼示例:
int cal(int n){
int sum = 0;
int i = 1;
for(; i <= n; ++i){
sum = sum + i;
}
return sum;
}
第2、3行程式碼都是常量級的執行時間,與n的大小無關,所以對於複雜度並沒有影響。迴圈執行次數最多的是第4、5兩行程式碼。
總的時間複雜度是O(n);
2,加法法則:總複雜度等於量級最大的那段程式碼的複雜度
程式碼示例:
int call(int n){ int sum_1 = 0; int p = 1; for(; p < 100; ++p){ sum_1 = sum_1 + p; } int sum_2 = 0; int q = 1; for(; q < 100; ++q){ sum_2 = sum_2 + q; } int sum_3 = 0; int i = 1; int j = 1; for(; i <= n; ++i){ j = 1; for(; j <= n; ++j){ sum_3 = sum_3 + i*j; } } return sum_1 + sum_2 + sum_3; }
第一段:sum_1部分,屬於常量的執行時間。對於一個執行次數固定的程式碼,即使迴圈10w,100w次,只要是一個已知數,跟n無關,照樣是常量級的執行時間。
第二段和第三段:時間複雜度分別是O(n)和O(n2).
總的時間複雜度等於量級最大的那段程式碼的時間複雜度。上例中:T(n) = O(n);
3,乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積
程式碼示例:
int cal(int n){ int ret = 0; int i= 1; for (; i < n; ++i){ ret = ret + f(i); } } int f(int n){ int sum = 0; int i = 1; for(; i < n; ++i){ sum = sum + i; } return sum; }
以上示例中,cal()中巢狀呼叫了f(),複雜度為O(n)的函式巢狀呼叫複雜度為O(n)的函式,總的複雜度T(n) = T1(n) *T2(n) = O(n*n) = O(n2)。
常見時間複雜度量級(按數量級遞增)
多項式量級:
- 常量階 O(1)
- 對數階 O(logn)
- 線性階 O(n)
- 線性對數階 O(nlogn)
- 平方階 O(n2)、立方階 O(n3)、... k次方階 O(nk):n的k次方
非多項式量級:
- 指數階 O(2n) 2的n次方
- 階乘階 O(n!)
以下幾個常見的多項式時間複雜度:
1,常量階 O(1)
執行次數是一個確定數字的程式碼,即執行時間不隨n的增長而增大,和n沒有關係,都記作 O(1)
2, O(logn)、 O(nlogn)
示例程式碼:
i = 1;
while( i <= n ){
i = i*2;
}
以上程式碼,當2的k次方大於n時,迴圈終止,即執行次數:
對於,如果一段程式碼的時間複雜度是,我們迴圈執行n次,則時間複雜度就是。
3, 、
例項程式碼:
in 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;
}
以上,無法事先評估 m 和 n 誰的量級大,所以無法利用加法準則,上面程式碼的時間複雜度就是
同理,當 m和n的程式碼出現巢狀,且無法判斷m和n誰的量極大,時間複雜度對應的就是
空間複雜度分析
空間複雜度,全稱漸進空間複雜度,表示演算法的儲存空間和資料規模之間的增長關係。
程式碼例項:
void print(int n){
int i = 0;
int[] a = new int[n];
for(i; i < n; ++i){
a[i] = i * i;
}
for(i = n-1; i >= 0; --i){
print out a[i]
}
}
以上程式碼中,第二行i對應申請了一個空間儲存變數,屬於常量階,和資料規模n沒有關係,因此可以忽略。
第3行申請了一個大小為n的int 型別陣列,除此之外,剩下的程式碼都沒有佔用更多的空間。
所以整段程式碼的空間複雜度就是
常見的空間複雜度是 、和,像、 這樣的對數階複雜度平時都用不到。