[笔记]《数据结构与算法之美》的第03课:复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?


这是我学习《数据结构与算法之美》的第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 谁的量级大,就只能将这两个复杂度相加或者相乘了。

空间复杂度

空间复杂度表示算法所需要的存储空间随着数据规模的变化趋势。空间复杂度相对容易分析一些,只有给变量申请空间的时候才会增加存储空间。

思考题

有人说,我们项目之前都会进行性能测试,再做代码的时间复杂度、空间复杂度分析,是不是多此一举呢?而且,每段代码都分析一下时间复杂度、空间复杂度,是不是很浪费时间呢?你怎么看待这个问题呢?

软件测试行业有句话是这么说的:质量是写出来的,不是测出来的。

性能,作为代码质量的一部分,应该是开发人员写出来的,而不是测试人员测出来的。性能测试只能作为一种代码性能的评估方式,类似老师给学生的试卷打分,学生在答题时就需要思考如何得高分,而不是先随便写个答案看老师给多少分,然后再看是哪道题做错了。

参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值