这是我学习《数据结构与算法之美》的第03课:复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
课程原文链接可点击此处.
为什么需要复杂度分析?
数据结构 和 算法 这两样东西配合起来,是为了在解决问题时,让程序运行的时间更快,或者占用空间更少,那么就需要有个度量程序在时间效率和空间效率上的方法。
将程序运行一遍后,记录运行的时长以及占用的内存空间,这种属于「事后统计法」,这种方式统计出来的时间和空间数据,依赖于测试环境,比如机器配置以及数据量大小,不利于程序员之间吹牛逼,你想想嘛,你写了个排序的代码,然后搞了点数据跑了一把,
你说:老子给 1GB 的数据排序只需要 1ms 就能完成,牛逼吧!当然了,我由于资金原因,只能买个 i9 处理器,买不起再便宜的了…
路人甲:滚吧,老子代码要是用 i9 跑,0.1ms 就能跑完,你用了那么久还好意思在这儿嘚瑟~
路人已:你测试的数据特么的都是相等的吧?
路人丙:。。。
你看,只要涉及到具体的环境信息,大家就很难达成统一,你吹你的,我吹我的,谁也不服谁。所以,需要有一种度量方法,与具体环境无关,让大家不用运行代码就能比出谁的效率高,达成统一的共识,这种度量方法也就是时间复杂度分析方法和空间复杂度分析方法。
大O复杂度表示法
先看以下代码中的 cal 方法:
int cal(int n) {
int sum = 0;
int i = 1;
int j = 1;
for (; i <= n; ++i) {
j = 1;
for (; j <= n; ++j) {
sum = sum + i * j;
}
}
}
这里必须有个假设:每一行代码每次执行所用的时间是相同的。(否则就太难计算了)
那么,cal 方法里每一行代码都执行了多少次呢?
第2行:1次。
第3行:1次。
第4行:1次。
第5行:第一个 for 循环,共执行了 n 次。
第6行:n次。
第7行:第二个 for 循环,共执行了 n*n 次。
第8行:n*n次。
总共:1+1+1+n+n+n*n+n*n = 2n2 + 2n + 3 次,cal 方法每行代码执行次数总和是关于 n 的一个函数,用 f(n)来表示,f(n) = 2n2 + 2n + 3。
假设每行代码每次执行所消耗的 CPU 时间是相同的,用 unit_time 来表示,那么 cal 方法执行完总共消耗的时间就是 f(n)*unit_time 了,是关于 n 的一个函数,用 T(n)来表示,T(n) = f(n)*unit_time = (2n2 + 2n + 3) * unit_time。
随着 n 的增大,T(n) 的增大趋势将主要取决于 n2 的变化,而 n2 的系数 2,以及 2n + 3 对 T(n) 的影响会随着 n 的增大而忽略掉,用大O符号来表示 T(n) 随着 n 增大时的变化趋势,cal 方法的执行时间 T(n) = O(n2),也就是 cal 方法拥有 n2 阶的时间复杂度。
O 表示 order,「阶」的意思。
分析一段代码的时间复杂度的3个实用方法:
- 只关注循环次数最多的一段代码,循环越多,n 的阶就越大。
- 加法法则:总复杂度 = 量级最大的代码的复杂度。
- 乘法法则:嵌套代码的复杂度 = 嵌套内外代码复杂度的乘积。
常见的时间复杂度
O(1)
O(1) 表示常量级的时间复杂度。常量级只是表示算法的时间复杂度与数据规模(n)无关,但并不代表常量级的算法时间复杂度就小,只是与 n 无关而已。
O(logn) 与 O(nlogn)
对数阶的时间复杂度比较难分析,需要判断出循环代码的是按 某个常量为底数的指数运算逐渐增长的,比如下面这段代码:
i=1;
while (i <= n) {
i = i * 3;
}
i 是以3为底数的指数运算增长的,i 按照 3x 的方式增大,当 x = log3n 时 i == n,运算结束,也就是这段循环代码运行次数是 log3n 次,用大 O 表示,即:O(log3n),由于对数的底数是可以换算的,log3n = log310 * log10n,忽略系数后, O(log3n) = O(log10n),为了方便,对数阶的时间复杂度都去掉了底数,使用 O(logn) 来表示。
O(m+n) 与 O(m*n)
代码里有两个同阶时间复杂度的循环的时候,比如一个循环 m 次,另一个循环 n 次,如果无法确认 m 和 n 谁的量级大,就只能将这两个复杂度相加或者相乘了。
空间复杂度
空间复杂度表示算法所需要的存储空间随着数据规模的变化趋势。空间复杂度相对容易分析一些,只有给变量申请空间的时候才会增加存储空间。
思考题
有人说,我们项目之前都会进行性能测试,再做代码的时间复杂度、空间复杂度分析,是不是多此一举呢?而且,每段代码都分析一下时间复杂度、空间复杂度,是不是很浪费时间呢?你怎么看待这个问题呢?
软件测试行业有句话是这么说的:质量是写出来的,不是测出来的。
性能,作为代码质量的一部分,应该是开发人员写出来的,而不是测试人员测出来的。性能测试只能作为一种代码性能的评估方式,类似老师给学生的试卷打分,学生在答题时就需要思考如何得高分,而不是先随便写个答案看老师给多少分,然后再看是哪道题做错了。
1798

被折叠的 条评论
为什么被折叠?



