如何分析和统计算法执行效率和消耗资源
wangjing013 opened this issue · comments
如何分析和统计算法执行效率和消耗资源
数据结构和算法本身解决的是“快”和“省”的问题, 即如何让代码运行得更快,如何让代码更省存储空间.
执行效率是算法一个非常重要的考量指标. 那如何来衡量你编写的算法代码的执行效率呢? 这里就要用到我们今天要讲的内容:时间、空间复杂度分析.
- 为什么需要复杂度分析?
通常一般用的方式是事后统计法, 也就是我把代码跑一遍,通过统计、监控,就能得到算法执行的时间和占用的内存大小.
按照正常来说这种衡量是正确的,但是这种方式存在弊端的.
- 依赖执行环境
同一套代码可能不同执行环境相应效率和占用资源不一样
-
测试结果受数据规模的影响很大
-
例如待排序数据的有序度不一样,排序的执行时间就会有很大的差别. 例如, 如果数据已经是有序的,那排序算法就不需要做任何操作.
-
同样如过测试数据规模太小,测试结果可能无法真实地反应算法的性能.
-
在这样情况下, 我们需要一个不用具体的测试数据来测试,就可以粗略估计算法的执行效率方法. 时间、空间复杂度分析法
大 O 复杂度表示法
算法的执行效率, 粗略地讲, 就是算法代码执行的时间. 如在在不执行代码的情况下,用肉眼得到一段代码的执行时间呢?
从CPU角度来看,代码每一行都执行着类似的操作: 读数据-运算-写数据
. 现在我们假设每一行执行的时间为 unit_time
, 先来看看下面的案例:
- 1.1 案例一
function cal(n) {
let sum = 0; // unit_time
let i = 1; // unit_time
for (; i <= n; ++i) { // unit_time
sum = sum + i; // unit_time
}
return sum;
}
第二行、第三行分别为: unit_time
, 第四、五行执行n
次,需要的时间为 2n * unit_time
. 上面代码总体执行的时间为: (2n + 2) * unit_time
. 可以看出来,所有代码的执行时间 T(n)
与每行代码的执行次数成正比.
- 1.2 案例二
function cal(n) {
let sum = 0; // unit_time
let i = 1; // unit_time
let j = 1; // unit_time
for (; i <= n; ++i) { // n * unit_time
j = 1; // n * unit_time
for (; j <= n; ++j) { // n * n * unit_time
sum = sum + i * j; // n * n * unit_time
}
}
}
按照上面每行代码执行时间为: unit_time
, 下面分析代码总执行时间为多少:
- 第二、第三、第四行代码分别为:
unit_time
总的时间为 3 * unit_time - 第五、第六行代码分别为:
n * unit_time
总的时间为 2n * unit_time - 第七、第八行代码分别为:
n * unit_time
由于代码存在嵌套循环,所以代码执行时间需要乘以外层执行次数. 也就是n * 2n * unit_time
.
上述代码总的执行时间为: T(n) = (3+ 2n + 2n^2) * unit_time
.
从上述两段代码, 我们可以得出规律: 所有代码的执行时间 T(n) 与每行代码的执行次数 f(n) 成正比
.
我们可以把这种规律总结成一个公式:
这就是大O 表示法.
T(n)
总的执行时间n
表示数据规模大小f(n)
表示每行代码执行次数的总和
现在把上面两个案例通过大O表示法分别对应为:
T(n) = O(2n + 2)
T(n) = O(3+ 2n + 2n^2)
这就是大O时间复杂度表示法
, 大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度
(asymptotic time complexity),简称时间复杂度
.
当 n
很大时,你可以把它想象成 10000、100000. 而公式中的低阶
、常量
、系数
三部分并不左右增长趋势, 所以都可以忽略. 我们只需要记录一个最大量级就可以了,如果用大 O 表示法表示刚讲的那两段代码的时间复杂度,就可以记为:T(n) = O(n)
; T(n) = O(n2)
。
时间复杂度分析
- 只关注循环执行次数最多的一段代码
我们在分析一个算法、一段代码的时间复杂度的时候,也只关注循环执行次数最多的那一段代码就可以了。
-
加法法则:总复杂度等于量级最大的那段代码的复杂度
-
乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积
几种常见时间复杂度实例分析
复杂度量级
- 常量阶 O(1)
只要时间不随着 n 的增大而增长, 这样代码的时间复杂度我们都可以记做 O(1); 或者说, 一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)
- 对数阶 O(log^n)
let i = 1
while (i < n) {
i = i * 2
}
-
线性阶 O(n)
-
线性对数阶 O(nlog^n)
-
平方阶 O(n^2)
-
立方阶 O(n^3)
-
指数阶 O(2^n)
-
阶乘阶 O(n!)