为什么需要复杂度分析
学习本节后,你需要理解复杂度分析的作用:它不是为了让代码看起来更“高级”,而是为了帮助我们判断一个方案在数据规模变大时还能不能稳定工作。
在小数据量下,很多写法都能正常运行。一个列表里只有 10 个元素时,从头到尾找一遍不会让人感觉慢。但如果列表里有 100 万个元素,而且查找操作每天发生很多次,原来“能跑”的代码就可能变成性能瓶颈。
复杂度分析关注的正是这个问题:当输入规模不断变大时,算法或数据结构的成本会怎样增长。
只看运行时间为什么不够
Section titled “只看运行时间为什么不够”最直接的效率判断方式是运行代码,然后看它用了多少秒。但这种方式有明显局限。
同一段代码,在不同电脑上运行时间不同;同一台电脑上,也会受到系统负载、编译器优化、解释器版本、输入数据分布等因素影响。如果只看某一次运行时间,很难得到稳定结论。
复杂度分析不依赖某一台机器,也不依赖某一次运行结果。它更关心增长趋势。
例如,一个方案处理 个数据时大约需要检查 次,另一个方案大约需要检查 次。即使在小规模数据下前者可能也很快,但当 非常大时,两者差距会迅速拉开。
复杂度分析关注增长趋势
Section titled “复杂度分析关注增长趋势”假设有两个查找方案。
第一个方案从头到尾逐个查找。如果有 个元素,最坏情况下可能要检查 次。
第二个方案每次都把查找范围缩小一半。如果有 个元素,检查次数大约随着 增长。
当 时,两者差距可能并不明显;当 时,差距就会非常明显。
这说明复杂度分析并不是精确计算每一条语句耗时,而是观察输入规模变大时,操作次数大致按什么速度增长。
为什么数据结构离不开复杂度
Section titled “为什么数据结构离不开复杂度”不同数据结构的价值,往往体现在操作代价不同。
数组适合按下标快速访问,但在中间插入元素可能需要移动大量数据。链表插入和删除节点比较灵活,但想访问第 个元素通常需要从头走过去。哈希表适合按键快速查找,但需要额外空间,也要处理冲突问题。
如果没有复杂度分析,我们只能模糊地说“这个快”“那个慢”。有了复杂度分析,就可以更清楚地表达:
- 按下标访问数组通常是 。
- 在线性表中顺序查找通常是 。
- 在有序数组中二分查找通常是 。
- 某些简单嵌套循环可能是 。
这些表达会贯穿后续所有章节。
复杂度不是唯一标准
Section titled “复杂度不是唯一标准”复杂度很重要,但不是唯一标准。
有时一个理论复杂度更好的方案,在实际小数据量下并不一定更快。因为真实程序还会受到常数开销、内存访问方式、实现复杂度、维护成本等因素影响。
例如,一个简单数组遍历虽然是 ,但它连续访问内存,实际运行效率可能很好。而某些复杂结构虽然在理论上有优势,但如果数据量很小,额外维护成本可能反而不划算。
复杂度分析用于判断算法或数据结构在输入规模变大时的成本增长趋势。
它让我们不再只凭感觉判断效率,而是能用更统一的方式比较不同方案。后续学习每一种数据结构时,都要同时关注它支持哪些操作,以及这些操作的时间复杂度和空间复杂度。