第二章-时间/空间复杂度

算法(Algorithm)是指用来操作数据、解决程序的一组方法。对于同一个问题,使用不同的算法,也许最终得到的结果是一样的,但在过程中消耗的资源和事件却会有很大的区别。我们通过 时间复杂度空间复杂度 来衡量不同算法之间的优劣。

时间复杂度: 是指执行当前算法所消耗的时间

空间复杂度: 是指执行当前算法需要占用多少内存空间

时间复杂度和空间复杂度只是 定性描述该算法的运行时间和空间 ,算法运行的时间和空间的一个 趋势 ,并不是一个实际的数值。

时间复杂度趋势图

时间复杂度

常见的时间复杂度量级有:

  • 常数阶 O(1)
  • 对数阶 O(logN)
  • 线性阶 O(n)
  • 线性对数阶 O(nlogN)
  • 平方阶 O(n^2)
  • 立方阶 O(n^3)
  • K次方阶 O(n^k)
  • 指数阶 O(2^n)

上面从上到下依次的时间复杂度越来越大,执行效率越来越低。

1. 常数阶 O(1)

无论代码执行了多行,只要没有循环等复杂结构,那这个代码的复杂度就是 O(1)

let i = 123; 
let j = 456;
console.log(i, j);

上述代码在执行的时候,它消耗的时间并不随着某个变量的增长而增长,永远只执行 1 次;无论这段代码有多长,都可以使用 O(1) 来表示它的时间复杂度

2. 线性阶 O(n)

单层 循环体内,代码会随着每次循环执行,它消耗的时间是随着n的变化而变化的,因此这是一个线性相关趋势,时间复杂度为 O(n)。

for (let i = 0; i < n; i++) {
   	console.log(i)
}

如果一个时间复杂度为 常数阶 O(1) 的算法 和 时间复杂度为 O(n) 的算法同时存在,那么整个时间复杂度为 O(1) + O(n) == O(n); 由于时间复杂度本身就是一个趋势,所以以时间复杂度增长最快的为准

3. 对数阶 O(logN)

查看以下代码:

let i = 1;
while (i < n) {
    i = i * 2;
}

从上面代码可以看到,在while循环里面,每次都将 i 乘以 2,乘完之后,i 距离 n 就越来越近了。我们试着求解一下,假设循环x次之后,i 就大于 2 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2(n)
也就是说当循环 log2(n) 次以后,这个代码就结束了。因此这个代码的时间复杂度趋势为:O(logn)

4. 线性对数阶O(nlogN)

线性对数阶O(nlogN) 其实非常容易理解,将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是 n * O(logN),也就是了O(nlogN)。

就拿上面的代码加一点修改来举例:

for (let i = 0; i < n; i++) {
   	let m = 1;
    while(m < n) {
        i = i * 2;
    }
}

5. 平方阶O(n^2)

将 O(n) 的代码再循环嵌套一遍,它的时间复杂度就是 O(n^2)

for (let i = 0; i < n; i++) {
   	for (let j = 0; i < n; i++) {
        console.log(i, j)
    }
}

这段代码其实就是嵌套了2层n循环,它的时间复杂度就是 O(n*n),即 O(n²)
如果将其中一层循环的n改成m,即:

for (let i = 0; i < m; i++) {
   	for (let j = 0; i < n; i++) {
        console.log(i, j)
    }
}

那么它的时间复杂度就变成了 O(m*n)

6. 立方阶O(n³)K次方阶O(n^k)

参考上面的O(n²) 去理解就好了,O(n³)相当于三层n循环,其它的类似

空间复杂度

空间复杂度是对一个算法在运行过程中临时占用存储空间大小的一个度量,同样反映的是一个趋势。

空间复杂度比较常用的有 O(1)、O(n)、O(n^2)

1. 空间复杂度 O(1)

如果算法执行所需要的临时空间不随着某个变量 n 的大小而变化,即此算法空间复杂度为一个常量,可表示为 O(1);

let i = 1;
let j = 2;
++i;
j++;
let m = i + j;

代码中的 i, j, m 所分配的空间都不随着数据量变化,因此它的空间复杂度为 O(1)

2. 空间复杂度 O(n)

const list = [];
for (let i = 0; i < n; i++) {
    list.push(i);
}    

这段代码中,定义了一个数组,并为这个数组添加了 n 个数据,因此这个数组占用的大小为 n。因此这段代码的空间复杂度为 O(n)

3. 空间复杂度 O(n^2)

const matrix = []
for (let i = 0; i < n; i++) {
    matrix.push([]);
    for (let j = 0; i < n; j++) {
        matrix[i].push(j);
    }
}

上面代码中,开始定义了一个数组,而后使用 for 循环再次定义了一个数组,相当于创建了一个二维数组,这个数组中的每一项都是一个空间度量为 n 的数组,因此它的空间复杂度为 O(n^2)

思考题

  1. 如果一段代码中有 3 个循环,他们循环的次数都是 n, 那么这段代码的时间复杂度是 O(3n) 还是 O(n)?
    • 分以下几种情况:
    • 如果是 3 个并列循环的代码,那么总的时间复杂度趋势为 O(n)
    • 如果是 3 个循环嵌套,那么总的时间复杂度趋势为 O(n^3)
  2. 假设每天睡觉前,你都会数 2 的次方,1、2、4、8……,每次都数到 n 才睡着,那么你数了几个数?时间复杂度是多少?
    • 数了:2^x = n ==> x = log2(n) ; x 从 0 算起 所以数了 log2(n) + 1 个数
    • 时间复杂度的趋势为 O(logN)

参考:https://zhuanlan.zhihu.com/p/50479555

posted @ 2020-11-05 11:28  公瑾当年  阅读(149)  评论(0编辑  收藏  举报