二分查找的改进
看完你能说清版本B、C的边界处理差异及为何更优
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
二分查找的改进
看完你能说清版本B、C的边界处理差异及为何更优
回顾:基础二分查找
上一页我们说要改进二分查找,那先回到原版长什么样:每次取区间中点比一下,根据结果三分岔——相等就中、太小往右、太大往左。看清它的比较方式和区间收缩,才知道哪里值得动手。
翻到中间页对比:目标词靠前就扔掉后半本,每次折半
版本 A 的代价
把基础版二分查找的每步比较摊开算账,暴露转向次数是隐藏的代价。
改进的构思
前面算过:版本 A 每深入一层要比两次——先问大小,再确认相等。可相等是极小概率事件,第二次比较岂不是白做了?
绝大多数货物走快速通道,少数可疑的才回头细查
版本 B 的策略
顺着箭头看:每个节点是一次判断,两条边只做一次比较,区间随之向左或向右收缩。
版本 B 的实现
C++ 代码展示版本 B:三分支合并为两分支,全靠循环不变式。
循环条件 1 < hi-lo 保证区间宽度递减仍可继续;第4行把相等并入右分支,用 hi=mi / lo=mi 保持不变式 A[lo]<=e<A[hi]。
版本 B 的语义
版本 A 失败时只答「没找到」,白跑一趟。版本 B 约定:不管中不中,都返回位置 mi。那失败时这个 mi 是什么?得把语义说清楚,调用方才能放心用。
没找到空位时,管理员指给你「最后一个被占的车位」
版本 A vs 版本 B
两个版本形似神异:比较次数从 1+x 降到 1,但语义和后继成本都变了,容易混。
- 每次迭代 1 次相等比较 + 1 次大小比较
- 命中即返回,平均查找更早结束
- 语义:MI/hi 处可直接命中返回
- 每次迭代只做 1 次大小比较,无相等判断
- 迭代次数固定为 ⌈log₂n⌉,与命中位置无关
- 语义:仅收敛到 MI,命中须额外验证
版本 B 遗留的问题
前页对比表已经显示:版本 B 平均省了比较次数,但仔细看两栏——命中左侧时每次都要深度比较,右侧却常一眼即中。省下的次数不等于省下的时间,这份「偏科」值得深挖。
一条陡一条缓,走的次数少不等于先到山顶
fib 式划分的构思
版本 B 按几何中点切,每次比较却代价不同——往深了猜一次很便宜,往浅了猜一次很贵。既然比较代价不均,为何不按代价均分区间?这就是 fib 式划分的出发点。
不看切在哪、看两边花费是否对等
版本 C 的流程
从入口区间出发,每轮按 fib 比例取 mi、只做一次比较,再深入左半或右半,循环至区间宽度为 1。
版本 C 的实现
版本 C 的代码:fib 式划分点如何计算与分支。
划分点不再取中点,而按 fib(k-1) 偏移;向左走只需多算 fib(k-2),右侧 fib 更短——用加法换掉除法。
正确性论证
前面三个版本的代码都写出来了,但「能跑」不等于「算得对」。这一页我们换个视角:不逐个试例子,而是找到一个贯穿全程的不变量,一次性证明版本 C 必然正确终止。
每一轮搜完,目标若在山里就必在剩余包围圈内
三个版本全景对比
A、B 都靠元素比较决定走向,C 却靠减法划界——两者语义与代价结构截然不同,值得系统对照。
- 每步做一次元素比较,按结果二分支
- 命中即返回,语义是『找到与否』
- 最坏约 log₂n 次比较,B 稳定不退化
- 每步只做一次减法,单分支确定一侧
- 到深度即停,语义是『最佳插入位置』
- 最坏约 log_φn 次减法,分摊更省
自测:版本选择
在有序向量上查找目标 e,若有多个命中位置,要求返回最靠后的那个,应选用哪个版本及理由?
要点收束
- ✓查找区间的语义决定了边界写法:往右缩进才安全
- ✓中点后移一位,最坏情况从 log n+1 降一档
- ✓fib 划分只用加减比较,代价换常数收益
- ✓正确性靠不变式:目标始终落在 (lo, hi) 之内
- ✓对比版本是收敛起点:三分支减到两分支是主线
课后思考
先自己想一阵,再看参考答案——答案只是起点。
参考答案回顾核心:语义统一后无需区分命中与失败,秩总在 [0, n],可直接作为插入点、前驱查询等接口,调用方少写分支。
参考答案应用层思考:版本 A 命中哪个不确定;版本 B 稳定返回最右侧不大于者;fib 版本只改变切分点,不改变语义,结果与 B 一致。
参考答案迁移到成本模型:比较代价高(字符串、磁盘 IO、缓存不友好)时每次比较都贵,常数才放大为可感知差距;否则 B 已够用。