时间复杂度O(logn)你真的搞懂了吗?几种常见的复杂度比较
了解O(logn)的意思
预先知道算法的复杂度是一回事,了解其后的原理是另一件事情。
不管你是计算机科班出身还是想有效解决最优化问题,如果想要用自己的知识解决实际问题,你都必须理解时间复杂度。
先从简单直观的 O(1) 和 O(n) 复杂度说起。O(1) 表示一次操作即可直接取得目标元素(比如字典或哈希表),O(n) 意味着先要检查 n 个元素来搜索目标,但是 O(log n) 是什么意思呢?
你第一次听说 O(log n) 时间复杂度可能是在学二分搜索算法的时候。二分搜索一定有某种行为使其时间复杂度为 log n。我们来看看是二分搜索是如何实现的。
因为在最好情况下二分搜索的时间复杂度是 O(1),最坏情况(平均情况)下 O(log n),我们直接来看最坏情况下的例子。已知有 16 个元素的有序数组。
举个最坏情况的例子,比如我们要找的是数字 13。
十六个元素的有序数组
选中间的元素作为中心点(长度的一半)
13 小于中心点,所以不用考虑数组的后一半
重复这个过程,每次都寻找子数组的中间元素
每次和中间元素比较都会使搜索范围减半。
所以为了从 16 个元素中找到目标元素,我们需要把数组平均分割 4 次,也就是说,
简化后的公式
类似的,如果有 n 个元素,
归纳一下
分子和分母代入指数
等式两边同时乘以 2^k
最终结果
现在来看看「对数」的定义:
为使某数(底数)等于一给定数而必须取的乘幂的幂指数。
也就是说可以写成这种形式
对数形式
所以 log n 的确是有意义的,不是吗?没有其他什么可以表示这种行为。
几种常见时间复杂度比较
做算法分析的时候经常用到各种时间复杂度如O(n), O(logn), O(nlogn), O(n^2), … 它们之间到底有多大的差别呢?下面这张图是一个直观的表达:
可见,各个常用的时间复杂度之间都存在着巨大的差异。从O(nlogn)到O(n),从O(n)到O(logn),都是性能上的巨大飞跃。
从另一个角度而言,大于$O(n^2) O(n^3) $时间复杂度的程序实际上都是不可用的。根据维基百科,现在最强的CPU每秒大概可执行428亿条指令4* , 而对于一个 的程序,当n=100时,就算有$2 ^ {100} $条指令,即$2 ^ {100} $ = 1.26765 * 条指令。这样的CPU大概要算1万亿年(10^12)。
算法的运行时间通常与下列函数成比例:
1 | 大部分程序的大部分指令之执行一次,或者最多几次。如果一个程序的所有指令都具有这样的性质,我们说这个程序的执行时间是常数。 |
---|---|
log log N | 可以看作是一个常数:即使N很多,两次去对数之后也会变得很小 |
logN | 如果一个程序的运行时间是对数级的,则随着N的增大程序会渐渐慢下来,如果一个程序将一个大的问题分解成一系列更小的问题,每一步都将问题的规模缩减成几分之一,一般就会出现这样的运行时间函数。在我们所关心的范围内,可以认为运行时间小于一个大的常数。对数的基数会影响这个常数,但改变不会太大:当N=1000时,如果基数是10,logN等于3;如果基数是2,logN约等于10.当N=1 00 000,logN只是前值的两倍。当N时原来的两倍,logN只增长了一个常数因子:仅当从N增长到N平方时,logN才会增长到原来的两倍。 |
N | 如果程序的运行时间的线性的,很可能是这样的情况:对每个输入的元素都做了少量的处理。当N=1 000 000时,运行时间大概也就是这个数值;当N增长到原来的两倍时,运行时间大概也增长到原来的两倍。如果一个算法必须处理N个输入(或者产生N个输出),那么这种情况是最优的。 |
NlogN | 如果某个算法将问题分解成更小的子问题,独立地解决各个子问题,最后将结果综合起来,运行时间一般就是NlogN。我们找不到一个更好的形容,就暂且将这样的算法运行时间叫做NlogN。当N=1 000 000时,NlogN大约是20 000 000。当N增长到原来的两倍,运行时间超过原来的两倍,但超过不是太多。 |
N平方 | 如果一个算法的运行时间是二次的(quadratic),那么它一般只能用于一些规模较小的问题。这样的运行时间通常存在于需要处理每一对输入数据项的算法(在程序中很可能表现为一个嵌套循环)中,当N=1000时,运行时间是1 000 000;如果N增长到原来的两倍,则运行时间将增长到原来的四倍。 |
N三次方 | 类似的,如果一个算法需要处理输入数据想的三元组(很可能表现为三重嵌套循环),其运行时间一般就是三次的,只能用于一些规模较小的问题。当N=100时,运行时间就是1 000 000;如果N增长到原来的两倍,运行时间将会增长到原来的八倍。 |
2的N次方 | 如果一个算法的运行时间是指数级的(exponential),一般它很难在实践中使用,即使这样的算法通常是对问题的直接求解。当N=20时,运行时间是1 000 000;如果增长到原来的两倍时,运行时间将是原时间的平方! |