空間複雜度
概念
表示演算法的儲存空間與資料規模間的增長關係
分析方法
只關注這段程式碼中申請的最大空間
void print(int n) {
int i = 0;
int[] a = new int[n];
for (i; i < n; ++i) {
a[i] = i * i
}
}
複製程式碼
上面程式碼第二行申請了一個空間儲存 i,它是常量階的,跟 n 無關,可以忽略。 第三行申請了一個大小為 n 的 int 陣列,除此之外,沒有申請其他空間,所以這段程式碼的空間複雜度為 O(n)。
時間複雜度
概念
表示演算法的執行時間與資料規模之間的增長關係
分析方法
- 只關注迴圈次數最多的一段程式碼
/**
* 函式中第一、二行程式碼執行 1 次
* 第三、四行程式碼執行了 n 次,所以總複雜度為 O(n)
*/
function cal(n) {
let sum = 0
let i = 1
for (; i <= n; i++) {
sum += i
}
return sum
}
複製程式碼
- 加法法則:總複雜度等於量級最大的那段程式碼的複雜度
/**
* 第一個 for 迴圈複雜度為常量級 O(1),第二個是 n,複雜度為 O(n)
* 所以總複雜度為 O(n)
* 注:只要是一個已知數(如:100,10000,1000000)都是常量級,複雜度為O(1)
*/
function cal(n) {
let i = 1
let sum_1 = 0
for (; i <= 100; i++) {
sum_1 += i
}
let sum_2 = 0
for (; i <= n; i++) {
sum_2 += i
}
return sum_1 + sum_2
}
複製程式碼
- 乘法法則:巢狀程式碼的複雜度等於巢狀內外程式碼複雜度的乘積
/**
* cal 中巢狀了函式 fn,fn 的總複雜度為 O(n),所以 cal 的總複雜度為 O(n²)
*/
function cal(n) {
let i = 1
let sum = 0
for (; i <= n; i++) {
sum += fn(i)
}
}
function fn(n) {
let sum = 0
for (; i <= n; i++) {
sum += i
}
return sum
}
複製程式碼
對數階時間複雜度分析
let i = 1
while (i <= n) {
i = i * 2
}
複製程式碼
根據上面的分析方法,找出執行次數最多的一行,為第 3 行; 可以看出 i 的值為每次乘以 2 的等比數列。當 2 的 k 次方大於 n 時,結束迴圈。
k 就為第 3 行的最大執行次數 2^k = n, k = log 以 2 為底的 n 次方。因為對數之間可以相互轉換,不管以什麼為底,我們都統一記為 O(logn)
最好、最壞情況時間複雜度
/**
* @param {Array} arr
* @param {Number} n 陣列的長度
* @param {*} x 要查詢的值
*/
function find (arr, n, x) {
let i = 0
let pos = -1
for (i; i < n; i++) {
if (arr[i] === x) {
pos = i
break
}
}
return pos
}
複製程式碼
上面這段程式碼在 arr[0] === x
時,為最好情況,此時時間複雜度為 O(1);
當 arr[n] === x
時,為最壞情況,此時時間複雜度為 O(n)
平均情況時間複雜度
上節程式碼要查詢 x 在陣列中的位置,有 n+1 種情況,分別是 x 在 0 ~ n-1 的位置中和不在陣列中。
x 在索引為 0 處時,遍歷 1 次,在索引為 1 時遍歷 2 次,總遍歷次數為 1 + 2 + 3 + ... + n + (n+1) = n(n+2)/2。(等差數列求和公式)。
平均每種情況要遍歷的次數為 n(n+2)/2(n+1) ≈ n/2,平均情況的時間複雜度就為 O(n)