空间复杂度
概念
表示算法的存储空间与数据规模间的增长关系
分析方法
只关注这段代码中申请的最大空间
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)