Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

顺序查找与二分查找

复习

  • 数组:连续存储,可按下标访问
  • 动态数组:动态数组在容量不足时自动扩容
  • 字符串:字符的序列

TL;DR

  • 顺序查找逐个比较,代价 O(n)
  • 二分查找每次砍掉一半,代价 O(log n)
  • 二分查找必须依赖有序数据
  • 它还依赖随机访问,因此链表上通常不划算

正文

  有了数组,最常见的操作就是“在里面找一个东西”。这一章看看两种查找方式,它们的差距非常能说明问题。

顺序查找:老老实实挨个看

  顺序查找(linear search)最朴素:从第一个开始,逐个和要找的目标比较,直到找到或看完。

  • 最好情况:第一个就是,O(1)
  • 最坏情况:找完都没有,O(n)

  它不需要数据有任何特殊排列,什么都能用。但数据一多,就慢。

二分查找:每次砍一半

  如果数据事先排好了序,就能用更快的方法——二分查找(binary search):

  1. 看中间那个元素
  2. 比目标小,就去右半边找;比目标大,就去左半边找
  3. 每次都能排除掉一半

  数据量无论多大,每次都除以 2,所以很快到达。从 100 万个元素里找,最多也就约 20 次。这就是 O(log n) 的威力。

它的两个前提

  二分查找这么快,却有两个硬性前提,缺一不可:

  • 数据必须有序:否则“砍一半”的推理不成立,不知道该往哪边找
  • 必须能随机访问:也就是能 O(1) 地跳到“中间”那一个

  第二条特别容易被忽略。数组可以随机访问,所以二分很合适;而链表虽然也能有序,却只能从头一个个走,跳到“中间”本身就要花 O(n),二分的优势就没了。这也是为什么“算法好不好用”,往往取决于“数据结构支不支持”。

小心边界

  二分查找思路简单,实现却很容易写错,尤其是各种边界:区间是左闭右开还是闭区间、循环什么时候结束、中点怎么算。“看起来简单”不等于“随手就能写对”,这正是需要仔细推导和验证的地方——就像前面强调过的,正确性得靠论证,而不是碰运气。

思考题 1

  二分查找为什么必须要求数据有序?

思考题 2

  为什么二分查找在链表上通常不划算?

小结

知识点

  • 顺序查找 O(n),不需要数据有序
  • 二分查找 O(log n),每次排除一半
  • 二分查找要求数据有序且可随机访问
  • 二分查找的边界条件容易出错

参考资料

  1. Wikipedia(zh):线性搜索:逐个比较的查找方式
  2. Wikipedia(zh):二分查找算法:每次排除一半的查找方式

思考题答案(仅供参考)

思考题 1

  因为二分查找依赖“中间元素与目标的大小关系”来判断该往哪半边找。只有数据有序,这个判断才能成立;否则元素的大小分布没有规律,排除一半就可能把目标也排除了。

思考题 2

  因为二分查找需要能 O(1) 地跳到中间元素。链表不支持随机访问,要到达中间位置必须从头逐个走,本身就要花 O(n),把二分每步 O(1) 的优势抵消了,所以通常不如直接顺序查找。

协议

  本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

封面图

设计师 | 南国微雪