时间复杂度的概念在大学里学习“数据结构与算法”课程时都学过。很多人在刷面经、刷八股的时候也已经把各种常用数据结构的时间复杂度烂熟于心。那么为什么还要“正确理解”它呢?
O(n^2) > O(nlogn) > O(n) > O(logn) > O(k) > O(1)
以上便是大家众所周知的对时间复杂度的比较。
一般来说, 能达到 O(nlogn) 的算法已经很不错了,因为随着n的无限增长, nlogn 的数值并不会比 n 大太多,可以基本认定为约定于 O(n)
O(k) 表示固定的常数次。
快速排序的时间复杂度是O(nlogn)
遍历一次数据的时间复杂度是O(n)
看起来一切都很自然,都是之前学习到的基础常识,不是吗?
但是, O(1) 一定就比 O(n) 要快吗?
让我们想像这样一个生活中的例子。
两个人比赛爬楼梯。
小甲需要赤手空拳从1楼跑到5楼, 小乙需要扛着两大袋水泥从1楼跑到2楼。
请问两人谁先到?
恐怕很可能是甲先到,尽管他需要跑5层楼,而乙只需要爬1层楼。(当然也不排除乙力气很大,反而先到的可能性)
这个例子中两人跑的楼层数,就相当于时间复杂度。时间复杂度这个指标的缺陷在于,它只关注了操作次数,而没有关注“每次操作的强度”。而 操作次数 x 单次操作的强度 才等于完整的耗时。
这套理解有两个经典的使用场景。
场景1:Hash table就一定快吗?
从hash table中查数据的时间复杂度是O(1)。因此,对于“判断某个值在不在集合内”的功能,基本都使用它来实现。
但是,如果某场景下可以确定这个值的全集总数量也不大,不妨改用数组。每次要判断时,遍历整个数组。尽管时间复杂度从O(1) 增长到O(n), 但遍历是一个很轻的操作,而计算哈希函数需要比较多的运营,反而会快一点。
场景2:插入排序
插入排序的时间复杂度是O(n^2),但它在数据集比较小时,也往往比快速排序等算法快。也是同理,它每次操作的“强度”比较轻,抵消了时间复杂度上的劣势。
除了上面说的“操作强度外”,另一个误区是“期望时间复杂度”与“最佳时间复杂度之间的区别”。
还是以排序算法为例,我们所说的往往是指“平均的”、“期望的”复杂度。
但是,对于不同的数据集和不同的排序情况,其时间复杂度也会有变化。
比如,假如是一个已经排好序的数组里随机插入了少量几个随机的数字,那么对它使用插入或冒汤排序可能也会比快速排序要快。因为在这种场景下这两者的时间复杂度也都会变得非常低。
因此,也要注意不同场景下,时间复杂度的变化。