还记得两三年前,我初看一本算法书,看到二分查找算法的复杂度时,我发现了了不得的东西:二分查找每次查询范围减少一半,需要查询的次数是
,它的复杂度是
。
我把它改成三分查找,每次查询两个数字与我的目标数字比较,那么我就可以把查询范围缩小成原先的 1 / 3。那这样我需要的查询次数就只有
,复杂度也是
,一下子就优化了这么多!
那如果是四分,五分,六分,七分,岂不是能无限优化到
?我要成为算法科学家啦!
当然,我的想法是错的。但是,我错在哪里呢?为什么三分的复杂度看起来低了,但是却没有人用?好了,正文开始。
要理解这个问题,首先要知道算法的复杂度到底如何确定。什么时候我们认为一个算法的复杂度高于另一个算法?什么时候两个算法的复杂度其实是一样的?
对于大部分算法来说,当规模
趋于无穷大时,问题的求解时间都会趋于无穷大。那么你说的这个无穷大,它到底有多大?复杂度的记法,我们当然都知道使用大
或者大
。但是,这两个符号,到底比较的是什么?这就是这篇文章首先要回答的问题。
一句话答案,我们所说的算法复杂度的大小,其实是算法复杂度的无穷大的阶数。
也就是说,如果一个算法 A 的复杂度的无穷大阶数比另一个算法 B 的无穷大阶数小,也就是说
,那么此时 A 的复杂度更小。下面举几个例子: ,所以
的算法比
的算法更优
,所以
的算法比
的算法更优
但是如果一个算法的复杂度函数比另一个算法的复杂度函数小,但他俩的无穷大阶数一样,那么其实两个算法的复杂度是一样的。这里也有几个例子: ,2 是常数。所以
的算法与
的算法复杂度一样
,4 是常数。所以
的算法与
的算法复杂度一样
好了,我们已经知道如何比较两个算法复杂度了。现在我们来看看为什么三分不比二分快。
答案很简单:二分法和三分法的渐进复杂度是一样的。
二分法的渐进复杂度为
,三分法的渐进复杂度为
,这两个复杂度都没有问题。那为什么说这两个是一样的呢?
看起来就是比
小,而且算出来也确实小很多啊?
Talk is cheap,让我们直接来比较一下它们的无穷大阶数:
是个常数,因此,两个函数是同阶无穷大。二分法和三分法的渐进复杂度其实是一样的。
这个结论也可以推广一下。任意取两个对数函数
与
,比较它们的无穷大阶数都会得到
,是个常数。因此对数函数的底对于算法的复杂度分析是没有意义的。
因此,无论是二分法,三分法,四分法,五分法...复杂度都是一样的,分的越多,代码反而越复杂,提高你的人脑处理复杂度,不值不值。
那么,这么看来,
和
是一样的,
和
是一样的。那算法复杂度分析时,常数都不重要吗?
并不是。
最常见的反例,
与
显然复杂度不同。因为
,
是更低阶的无穷大。
当然,大部分人可能不会在
与
的比较上犯错误,但是下面这个比较,错的人就不少了。
和
哪个复杂度更大?
很多人以为这两个复杂度是一样的,因为常数不在指数位置上,看起来只是不同的常数做了指数操作,而常数一般对算法的复杂度没有影响。
但是实际上
的无穷大阶数比
要小。这两者复杂度并不一样。
而有人可能还觉得,就算复杂度不一样,可能也差不太多吧?都是指数函数,本身复杂度已经很高了啊?
但是实际上并不是这样。我们可以把
,
,
和
放到一起比较一下。
相对于
,很多人已经认为是增长了太多,不太能接受了。那么
相对于
增长了多少呢?我们其实可以比较一下这两个“增长”之间的无穷大阶数。
可见, 比
差,但是它们之间相差的时间倍数只是在线性增长。而
相对于
,相差的则更多,它们之间相差的时间倍数还是在指数增长!
这是什么概念呢?
如果有一个问题规模为
,
算法可以用 1 秒解决,那么使用
的算法,你需要 20 秒才能解决。这已经让大部分人无法接受了。
但是,如果有一个问题规模为
,
算法可以用 1 秒解决,那么使用
的算法,你需要 3325 秒才能解决!
所以,你现在还觉得,
和
差不多吗?