复杂度无非是空间,时间复杂度。 掌握了时间,空间复杂度的分析,基本算掌握了数据结构与算法的一半内容。
之所以引入这几个复杂度概念,是因为,同一段代码,在不同输入的情况下,复杂度量级有可能是不一样的。
分析一段代码的时间,空间复杂度时候,最需要关注的是循环次数最多的代码。
时间复杂度的全称是渐进时间复杂度,表示算法的执行时间与数据规模之间的增长关系。
类比一下,空间复杂度全称就是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。
如何分析一段代码的时间复杂度?
1. 只关注循环执行次数最多的一段代码
2. 加法法则,总复杂度等于量级最大的那段代码的复杂度。
3. 乘法法则,嵌套代码的复杂度等于嵌套内外代码复杂度的乘积。
对于上面罗列的复杂度量级,我们可以粗略地分为两类,多项式量级和非多项式量级。其中,非多项式量级只有两个:和
。
我们把时间复杂度为非多项式量级的算法问题叫作 NP(Non-Deterministic Polynomial,非确定多项式)问题。
- O(1)
首先你必须明确一个概念,O(1) 只是常量级时间复杂度的一种表示方法,并不是指只执行了一行代码。比如这段代码,即便有 3 行,它的时间复杂度也是 O(1),而不是 O(3)。
我稍微总结一下,只要代码的执行时间不随 n 的增大而增长,这样代码的时间复杂度我们都记作 O(1)。或者说,一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
2. O(logn)、O(nlogn)
对数阶时间复杂度非常常见,同时也是最难分析的一种时间复杂度。我通过一个例子来说明一下。
实际上,不管是以 2 为底、以 3 为底,还是以 10 为底,我们可以把所有对数阶的时间复杂度都记为 O(logn)。为什么呢?
我们知道,对数之间是可以互相转换的,
下面开始进行互换,首先设 a 为 2 将所有数换为以 2 为底的。将含有 n 的分母移出来。再使用换底公式将 log_23log23 换为以 3 为底的。又因为分母的 log_33=1log33=1 所以我们换底就成功拉。
参考: https://blackyau.cc/24.html#ologn-onlogn
如果理解了上面讲的 O(logn),那 O(nlogn) 就很容易理解了。O(nlogn) 也是一种非常常见的算法时间复杂度。比如,归并排序、快速排序的时间复杂度都是 O(nlogn)。
如何分析空间复杂度
我们常见的空间复杂度就是 O(1)、O(n)、O(n2 ),像 O(logn)、O(nlogn) 这样的对数阶复杂度平时都用不到。而且,空间复杂度分析比时间复杂度分析要简单很多。所以,对于空间复杂度,掌握刚我说的这些内容已经足够了。
1.最好情况时间复杂度(best case time complexity):在最理想的情况下,执行这段代码的时间复杂度。
2.最坏情况时间复杂度(worst case time complexity):在最糟糕的情况下,执行这段代码的时间复杂度。
3.平均情况时间复杂度(average case time complexity)
最好情况时间复杂度和最坏情况时间复杂度对应的都是极端情况下的代码复杂度,发生的概率其实并不大。为了更好地表示平均情况下的复杂度,我们需要引入另一个概念:平均情况时间复杂度。
4.均摊时间复杂度(amortized time complexity)
因为,要查找的变量 x 可能出现在数组的任意位置。如果数组中第一个元素正好是要查找的变量 x,那就不需要继续遍历剩下的 n-1 个数据了,那时间复杂度就是 O(1)。但如果数组中不存在变量 x,那我们就需要把整个数组都遍历一遍,时间复杂度就成了 O(n)。
所以,不同的情况下,这段代码的时间复杂度是不一样的。
为了表示代码在不同情况下的不同时间复杂度,我们需要引入三个概念:最好情况时间复杂度、最坏情况时间复杂度和平均情况时间复杂度。
数据结构整体概览