EXPERIMENT 01 · BINARY SEARCH

二分查找(Binary Search)图解:窗口怎样缩小

同一组数字,两种更新规则。看一个窗口抵达答案,另一个停在原地。

本次寻找13
有序数组
0 起始下标

同步对照 / SAME INPUT, TWO RULES

FRAME 01 / 05

A跳过 mid,继续缩小

lo = mid + 1 / hi = mid - 1

比较中
数组 1、3、5、7、9、11、13。lo 为 0,hi 为 6,mid 为 3。a[3] = 7 < 13;下一步 lo = mid + 1 = 4。跳过已比较的 mid,窗口严格缩小。1031527394115136midlohi

窄屏可左右滑动图示;下方数值显示完整区间

a[3] = 7 < 13;下一步 lo = mid + 1 = 4。跳过已比较的 mid,窗口严格缩小。

lo0hi6剩余7

B留下 mid,窗口会停滞

lo = mid / hi = mid

比较中
数组 1、3、5、7、9、11、13。lo 为 0,hi 为 6,mid 为 3。a[3] = 7 < 13;下一步 lo = mid = 3。错误更新仍保留已比较的 mid。1031527394115136midlohi

窄屏可左右滑动图示;下方数值显示完整区间

a[3] = 7 < 13;下一步 lo = mid = 3。错误更新仍保留已比较的 mid。

lo0hi6剩余7
剩余搜索区间正在比较的 mid已排除数字下方的小字是下标

第 1 步。正确轨道:a[3] = 7 < 13;下一步 lo = mid + 1 = 4。跳过已比较的 mid,窗口严格缩小。 错误轨道:a[3] = 7 < 13;下一步 lo = mid = 3。错误更新仍保留已比较的 mid。

TAKEAWAY

每一步,都要让问题变小。

保留 mid 并不一定每次都失败,但一旦新区间与旧区间相同,下一轮会重复同一次比较。本演示识别停滞后停止,不实际执行无限循环。

TRY TO EXPLAIN

[1, 3] 找 3,为什么 lo = mid 会卡住?

第一次 mid = 0,a[0] = 1。lo = mid 使窗口仍为 [0, 1];正确的 lo = mid + 1 将窗口变为 [1, 1],下一轮命中 3。

看完图,带走这些结论

二分查找在有序数组中比较中点,每轮排除不可能的一半。本页闭区间版本命中时返回一个匹配下标,否则返回 -1。

适用条件与边界

输入按非下降顺序排列,允许重复;数组可随机访问。重复目标只保证找到一个,不保证最左或最右。空数组直接返回 -1。图形输入限制为 1–8 个 ±999 整数,这不是算法本身的限制。

始终保持的规律

目标若存在,就仍在闭区间 [lo, hi] 中。正确更新为 lo = mid + 1 或 hi = mid - 1;lo > hi 时返回 -1。

逐步例子

  1. 在 [1, 3, 5, 7, 9] 中找 9:lo = 0,hi = 4,mid = 2,a[mid] = 5 < 9。
  2. 排除中点及左侧:lo = 3,hi = 4,mid = 3,a[mid] = 7 < 9。
  3. 再令 lo = 4,hi = 4,mid = 4,命中 9,返回下标 4。

复杂度

迭代查找最坏时间 O(log n),额外空间 O(1);空数组 O(1)。不包含预先排序的成本,也不包含页面为动画保存轨迹的开销。

常见误区

  • 闭区间未命中后写 lo = mid 或 hi = mid,可能保留同一窗口而停滞。不要混用闭区间和半开区间模板。
  • 未排序就二分,排除一半的依据不成立;排序还会改变原始下标。
  • C++ 使用 lo + (hi - lo) / 2 时,仍须保证下标和差值可表示;有符号整数溢出是未定义行为,本页小整数不模拟它。

自测与答案

[1, 3] 查找 3,第一次 mid = 0。若写 lo = mid,下一轮窗口是什么?

仍是 [0, 1],中点也仍是 0。窗口没有缩小。改成 lo = mid + 1 后窗口变为 [1, 1],下一轮命中。

参考资料

阅读 Notes:推导与 C++ 代码 · 纯文字 Markdown 阅读版