前言
在计算机领域中,经常用时间复杂度和空间复杂的来客观描述某个算法或者某段代码的执行效率(执行时间)和内存消耗(内存使用情况)。
一. 大 O 复杂度表示法
在分析某个算法或者代码的执行效率或者内存消耗
时,经常能看到 O(1)、O(n) 这类似的表示,它们就是复杂度的表示方法。那它们又是怎么得到的呢?
现在,用大 O 复杂度表示法,来分析下面这段代码的执行时间。
function cal(n) {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum = sum + i;
}
return sum;
}
假设:
每条语句的执行时间都是 t,
我们用 :
T(n) 来代表执行这段代码执行所需的时间,
f(n) 来代表这段代码总共执行了多少条语句,
那么:
可以很容易的得到:
T(n) = f(n) * t。
在上面那段代码中,第 3 行和第 7 行的执行次数分别是 1,第 4 行和第 5 行的执行次数分别是 n,
那么:f(n) = 1 + 1 + n + n = 2 + n,
故,示例代码的执行时间为:T(n) = (2 + n) * t。
代码的执行时间已经得到了。大功告成了吗?
回顾时间复杂度的概念,会发现,其实并没有足够简洁地表示出数据规模的增大与执行时间的增长趋势,还需要对上面的 T(n) = (2 + n) * t
进行进一步的分析。
由于随着数据规模的增大,t 其实并不会变化,对执行时间的变化趋势没有影响;所有代码的执行时间 T(n) 与代码的执行总次数 f(n) 成正比
,用O() 表示正比的关系,关于 T(n) 可以得到另一个公式:
T(n) = O(f(n))
在上述例子中,f(n) = 2 + n,则:T(n) = O(2 + n);再去除 f(n) 中的常数项、低阶项、系数,只保留最高阶的项,就能的到:T(n) = O(n) 。其中:O(n) 就是示例代码的时间复杂度啦!
用类似上面的分析方法,也能得到空间复杂度。
第 2 行和第 3 行分别使用了一个存储空间,使用的总空间为 2,是个常数,故把空间复杂度表示为 O(1)。
二. 时间复杂度
时间复杂度,也被称为渐进时间复杂度,反映的是随着数据规模的增大,算法所需的执行时间的变化趋势。
1. 计算时的通用的原则
采用上面提到的一步一步分析的方法,的确可以正确地得到时间复杂度,但是,其实是有一些通用的原则,可以帮助我们快速高效的得到最终的时间复杂度。
-
循环优先:重点关注循环次数最多的那段代码
-
加法原则:多段并列的代码段,取量级最高那个作为最终的时间复杂度
-
乘法原则:嵌套代码段的时间复杂度为内外代码时间复杂度的乘积
2. 常见的时间复杂度
常见的时间复杂度以及它们的执行效率由高到低为(越低阶的算法的执行效率越高):
O(1) > O(logn) > O(n) > O(nlogn) > O(n的平方) > O(2的n次方) > O(2!)
随着数据规模的增大,时间复杂度为 O(2的n次方) 或 O(2!) 的算法的执行时间会呈现出指数级的爆炸增长。
1. O(1) 常量级
一般情况下,只要算法中不存在循环语句、递归语句
,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
2. O(logn) 对数阶
循环的执行次数为等比数列的总项数。
例如下面这段代码:
function fn(n) {
let i=1;
while (i <= n) {
i = i * 2;
}
}
分析一下,首先是循环优先原则,执行次数最多的代码为第 3 和 第 4 行。每次循环,i 的值都乘以 2 ,直到大于 n 才结束循环,这个过程中,i 的取值如下:
这里的 x 就是代码的总项数,也就是第 3 和第 4 行代码的各自执行次数了。
则 :x = O(log2n) 。
对数之间是可以互相转换的,在对数阶时间复杂度的表示方法里,我们忽略对数的“底”,统一表示为 O(logn)。
3. O(n) 线性对数阶
单层线性循环。
4. O(nlogn) 线性对数阶
一段时间复杂度为 O(logn) 的代码外面嵌套了一个复杂度为 O(n) 的循环。
5. O(m + n)、O(m * n) 涉及 2 个数据规模
O(m + n) 由于无法确定哪个数据规模对执行时间的影响更大,故加法原则失效。
O(m * n) 乘法原则依旧生效。
3. 不同情况下的时间复杂度
最好(情况)时间复杂度、最坏(情况)时间复杂度、(加权)平均(情况)/期望时间复杂度、均摊时间复杂度(涉及摊还算法)。
二. 空间复杂度
空间复杂度,也被称为渐进空间复杂度,反映的是随着数据规模的增大,算法所需的存储空间的变化趋势。
空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。
空间复杂度的计算,就是计算某段代码或者算法在运行过程中,使用到的总的存储空间数。
重点关注点在于使用的内存数量与数据规模之间的关系,也是采用大 O 表示法来表示。因此,也是要忽略常数阶、低阶、系数,只保留最高阶哦!