![1ef898323a8688dac6794ead931de9e6.png](https://i-blog.csdnimg.cn/blog_migrate/172b313c644703ee368e00e4885cb5bd.jpeg)
一:数据结构之复杂度分析
网上一直有一个非常火的话题:什么样的开发者才能算是一个优秀的开发者。一个优秀的开发者:首先完成业务功能是基本,然后写的代码足够优雅(可读性要好,在这里多说一句,你写的代码不仅仅是给机器看的,也是给人看的),最后效率一定要高。
那么问题来了。我们如何衡量代码的效率是否足够高呢?这里就需要提到重点了:时间,空间复杂度分析。
时间和空间复杂度分析是数据结构与算法的核心所在。
1:为什么需要复杂度分析?
平常工作中,写好某一功能后,本地代码跑一遍,通过分析就能够得到代码执行的时间和占用的内存空间。那么为什么我们还需要进行复杂度分析呢?难道复杂度分析出来的数据比实际跑一次更准确。NO,绝对不是。
说一下前者有什么局限之处。
l 测试的结果非常依赖环境。同样的代码,i9处理器肯定比i3处理器要执行的快得多。如果迁移到另外的机器上,i9处理器换成了i5处理器,它的执行效率可能远远比不上i9了。
l 测试结果受数据规模的影响很大。执行一个排序算法,如果本身需要排序的数据就已经排好序了,那么执行的时间就很快。如果测试的数据规模太小,测试结果就无法反映算法的性能。
所以,我们需要一种可以不用具体的测试数据测试就可以粗略估算算法执行效率的方法。
2:大O复杂度表示法
通常情况下,相同条件,算法执行的时间越短效率越高。我们如何在不允许代码的情况下,用”肉眼“得到一段代码的执行时间呢?
下面有一段代码,我们来分析一下:
![d72e33e9fbaa95bd906e16483426654f.png](https://i-blog.csdnimg.cn/blog_migrate/bee46ca4820716961e4397a5a9b02319.jpeg)
我们假设每行代码的执行时间都一样,为unit_time(单位时间),第7行和第8行只会执行一次,也就是1个unit_time;第9行和第10行会执行n次,所以需要2n*unit_time。对于上述代码总的执行时间就是(2n + 2)*unit_time。
因为执行的次数不可能为负,所以代码执行时间T(n)与每行代码的执行次数成正比。
按照上面的学习思路,看下面的这段代码:
![8cbb2a3abc46734b8459047870ca7410.png](https://i-blog.csdnimg.cn/blog_migrate/dde69f48d6b504fd02d267b8022c3afc.jpeg)
每行代码的执行时间依旧是unit_time,第8,9,10行执行的次数是1次,第11,12行执行了n遍,需要2n*unit_time,第13,14行是循环嵌套,需要执行2n^2 *unit_time,所以T(n) = (2n^2 + 2n +3) * unit_time。
小结:所有代码的执行时间T(n)与每行代码的执行次数n成正比
公式:T(n) = O(f(n))
T(n)表示代码的执行时间,n表示数据规模,f(n)表示每行代码执行次数的总和。O表示代码的执行时间T(n)与f(n)表达式成正比。
所以,第一段代码T(n) = O(2n + 2),第二段代码T(n) = O(2n^2 + 2n + 3)
重点:大O时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随着数据规模增长的变化趋势,所以,也叫做渐进时间复杂度。使用大0表示法可以忽略公式中的低阶,常量,系数。刚刚讲的示例公式可以记为:T(n) = O(n),T(n) = O(n^2)
3:时间复杂度
进行时间复杂度分析的有效方法?
3.1 只关注循环执行次数最多的一段代码
分析一个算法,一段代码,只关注循环执行次数最多的那一段就可以了。
以上述第一段代码为例,第7行和第8行只会执行一次,第9行和第10行会执行n次,所以时间复杂度就是O(n)。
3.2 总的复杂度等于量级最大的那段代码的复杂度(加法法则)
如果说一段代码中有多个循环,那么最终的时间复杂度就取量级最大的那个。
3.3 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积(乘法法则)
如下代码:
![2f849410c0ee608daad64beb517a3e49.png](https://i-blog.csdnimg.cn/blog_migrate/cf67f297ca0c804c15b2f05afa25c214.jpeg)
按照上诉所学,单独看cal方法,时间复杂度是T(n) = O(n),但是循环中的f()函数本身不是一个常量行为。f方法的时间复杂度也是T(n) = O(n)。所以,整个cal的时间复杂度就是T(n) = O(n2)
3.4 时间复杂度
复杂度量级如下:
l 常量阶 O(1)
l 对数阶 O(logn)
l 线性阶 O(n)
l 线性对数阶 O(nlogn)
l 平方阶 O(n2) 立方阶O(n3) ....k次方阶O(nk)
l 指数阶O(2n)
l 阶乘阶 O(n!)
4:空间复杂度
空间复杂度相对于时间复杂度要简单的多。
空间复杂度就是渐进空间复杂度,表示算法的存储空间与数据规模之间的增长关系。
常见的空间复杂度:O(1) O(n) O(n2) O(logn) O(nlogn)
举个例子
![04974a4d28d7288e4df1f24ea9c14a51.png](https://i-blog.csdnimg.cn/blog_migrate/67d963d3da6b78f3a323630be4773635.jpeg)
和时间复杂度一样,常量级的忽略。第9行申请了一个大小为n的内存地址,除此之外,剩下的代码没有占用更多的空间。所以整段代码的空间复杂度就是O(n)。第10,11行执行的时间复杂度是(2n)*unit_time。不要把时间复杂度和空间复杂度混淆。
5:时间复杂度扩展
某些情况下,上面的时间复杂度分析方法还不能很好的解决问题。下面还有一些不太常用的关于时间复杂度的一些扩展。
l 最好情况时间复杂度
l 最坏情况时间复杂度
l 平均情况时间复杂度
l 均摊时间复杂度
最好最坏情况时间复杂度
![2b8d15e3da8f85e6d3d50bf3117da6c6.png](https://i-blog.csdnimg.cn/blog_migrate/ceb471a68a7f512b358a8bad0c2b56c1.jpeg)
上述代码实现的功能是在array数组中查找值为x的下标。如果上述代码中没有加上break,按照前面所学,它的时间复杂度应该是T(n) = O(n)。但是加上break后,它的时间复杂度还是O(n)吗?显然可能不是了。如果查找的数组中第一个值便匹配成功了,立即跳出循环,时间复杂度就是O(1); 如果最后一个值才匹配成功,那么它的时间复杂度就是O(n)。
所以:最好情况时间复杂度就是,在最理想的情况下,执行这段代码的时间复杂度。最坏情况时间复杂度就是,在最糟糕的情况下,执行这段代码的时间复杂度。