复杂度分析
数据结构和算法本身解决的就是“快”和“省”的问题,如何让代码运行的更快且占用更少的存储空间。所以你设计的数据结构和算法的好坏的衡量标准是什么呢?——时间和空间复杂度。
时间、空间复杂度分析
有很多代码监控和统计工具能得到算法的执行时间和占用内存大小,为什么还要人工去分析复杂度? ——有局限性。
- 得出的测试结果依赖测试环境(CPU、GPU等);
- 测试结果受数据规模的影响。 所以我们需要一个不受影响的分析方法。
大O复杂度表示法:
粗略假设每行代码的执行时间是一定的为unit_time(实际上每行代码对应的CPU执行个数、时间都不一样)。一段代码的时间复杂度就可以看成是这段代码需要执行的总行数。 
总结规律的公式: T(n) = O(f(n)) 表示总执行时间和总执行次数成正比。 T(n)表示总执行时间 N表示数据规模的大小 f(n)表示所有代码的执行总次数,因为它是一个公式所以用f(n)表示 O表示正比关系
所以: 例1 :T(n) = O(2+2n) 例2:T(n) = O(2*n^2+2n+3)
以上就是大O时间复杂度表示法。 可以看出大O时间复杂度表示法并不表示代码真正的执行时间,而是表示代码执行时间随着总执行次数(也可以说是数据规模n)增长的变化趋势,因此也叫渐进式时间复杂度。
当n很大时,f(n)中的低阶2n、常量3、系数2三部分并不会影响总执行次数的增长趋势的,所以可以忽略,只需要记录一个最大量级就可以估算出总执行时间了。因此上边例子中的大O复杂度可以记为:T(n) = O(n); T(n) = O(n^2);
时间复杂度分析方法:
- 只关注循环执行次数最多的一段代码:上面刚说了,时间复杂度只需要关心最大阶的量级就行了。因此在分析一段代码的时间复杂度时,只关注循环执行次数(最大阶)最多的那一段代码就行了。这段核心代码执行次数的n的量级就是这段代码的时间复杂度了。
- 加法法则:总复杂度等于量级最大的那段代码的复杂度。几段代码肯定都有自己的执行次数,他们加起来的总的时间复杂度就取量级最大的那段代码的时间复杂度。
- 乘法法则:嵌套代码(一段代码中嵌入了另一段代码)的复杂度等于嵌套内外代码复杂度的乘积。
T(n) = T1(n)*T2(n) = O(n……2)
几种常见的时间复杂度量级
-
O(1) 表示常量级时间复杂度,不是只执行一次。一般只要算法中没有循环和递归等语句,即使有成千上万行代码,时间复杂度也是O(1)。
-
O(logn)和O(nlogn)对数阶时间复杂度很常见,
I的值就是一个等比数列: 因此3行代码执行了x次, 2^x = n因此 x = log2n 时间复杂度O(log2n) 同理如下: 时间复杂度O(log3n)不管以2为底、3为底还是10为底,统一把所有的对数阶的时间复杂度记为O(logn)。为什么呢?因为对数之间是可以相互转化的 log3n = log32 * log2n 去掉常量复杂度是一样的。因此忽略底统一O(logn)。 O(nlogn)根据乘法法则,对时间复杂度是logn的代码执行n次。比如归并排序、快速排序的时间复杂度。 -
O(m+n)和O(m*n)代码的时间复杂度由两个数据规模决定。
按照加法法则应该是在 m和n中找量级最大的作为整个代码的复杂度。但是m和n无法确定谁大,这是就不能简单的利用加法法则了,它的复杂度就是加和了O(m+n)。但是乘法法则仍适用。
空间复杂度分析
同理时间复杂度,空间复杂度也叫渐进式空间复杂度,表示算法所用的存储空间和数据规模之间的增长关系。空间复杂度就和代码中所占存储空间总字节数(简单的可以看成是块数)成正比了。同样找最大量级。注:是指除了原本的数据存储空间外,算法运行还需要的额外存储空间。如作为参数传入的大小为n的数组,是不算在内的。
第2行申请了一个常量的存储空间,3行申请了n个存储空间之后仅是使用没有占用更多空间。同时间复杂度得出它的空间复杂度O(n)。
比较常见的空间复杂度就是:O(1) 、O(n) 、O(n^2)
复杂度效率比较
从低阶到高阶:O(1) O(logn) O(n) O(nlogn) O(n^2) 如上图可看出一般越高阶效率越低。
计算时间空间复杂度的好处:尽可能从代码级别、根本上、理论上让保证代码的执行效率,并不是一定能确保在不同环境下都是O(1)的效率就一定优于O(n)。尽可能让代码高效。
最好、最坏情况下时间复杂度
平均时间复杂度
也叫甲醛平均时间复杂度或期望时间复杂度,因为对于每一种可能都乘上了它出现的概率。 复杂度是1、2……n的概率是1/2n(在与不在数组内的概率都是是1/2,而在数组内的哪个位置上的概率都是一样的也就是1/n,因此匹配上的概率就是1/21/n=1/2n)。所以每种情况的时间复杂度她出现的概率=平均时间复杂度。O(n)
均摊时间复杂度
👆的时间复杂度算法仅有在不确定复杂度的时候才使用,均摊的使用场景会更特殊有限。
根据经验,均摊时间复杂度一般都等于最好情况的时间复杂度(把最坏的时间复杂度均摊到大多数情况下)
均摊就是特殊的平均了,均摊还是平均,要看具体的分析。
非递归代码的多项式求和技巧
递归代码
从原始递推方程开始,反复将对于递推方程左边的函数用右边的等式代入,直到 得到初值,然后将所得的结果进行化简。
例如在调用归并排序mergeSort(a,0,n-1)对数组a[0...n−1] 排序时,执行时间T(n) 的递推关系式为:
T ( n ) = { O ( 1 ) , 当 n = 1 T ( ⌈ n 2 ⌉ ) + T ( ⌊ n 2 ⌋ ) + O ( n ) , 当 n >= 2其中,O(n) 为merge()所需要的时间,设为cn (c为正常量)。因此:
T ( n ) = 2 T ( n 2 ) + c n = 2 ( 2 T ( n 4 ) + c n 2 ) + c n = 2 2 T ( n 4 ) + 2 c n = 2 3 T ( n 8 ) + 3 c n = . . . = 2 k T ( n 2 k ) + k c n = n O ( 1 ) + c n log 2 n = O ( n log 2 n ) , ( 假 设 n = 2 k , 则 k = log 2 n )时间复杂度仅是个粗略的计算,因此我们可以忽略一些细节,如假设问题规模n=2……k,从而得出时间复杂度。