封神冰川的深处,狡猾的谜语者(Riddler)心里想了一个 1 到 999 之间的数字,只给你 10 次机会去猜中它。每猜一次,他会用召唤怪物的方式暗示你"猜大了"还是"猜小了"。999 个可能、10 次机会——如果一个一个地试,永远也来不及。这一单元你将掌握计算机科学里最优雅、最重要的搜索算法之一:二分查找(binary search)。它的威力惊人——哪怕有一百万个数字,它也能在 20 次以内锁定答案。
scout 说明目标更大、munchkin 说明目标更小)判断猜测的高低;据此调整搜索区间的上下边界;理解为什么这样能在有限次数(对数级)内逼近并命中目标。
二分查找是一种在有序范围里快速定位目标的算法。它的名字里"二分"就是"一分为二"的意思:每一步都猜正中间的那个数,然后根据"太大/太小"的提示,果断丢掉一半不可能的区间,只在剩下的一半里继续找。就这样一半、一半地砍,范围飞快地缩小,很快就只剩下目标本身。
如果从 1 开始一个一个往上猜(线性查找),最坏情况要猜 999 次。而二分查找每次把范围减半,999 → 500 → 250 → 125 → … 只需约 10 次 就能覆盖全部 999 个数字。这就是关卡只给 10 次机会、却依然稳赢的原因——它逼着你使用二分查找。
从 1 试到 999,一次只排除一个。最坏要 999 次——远超 10 次机会,必输。
每次砍掉一半,约 10 次覆盖 999 个数。恰好在机会用完前锁定目标。
二分查找要时刻记住"目标还可能在哪个范围内"。我们用两个变量描述这个范围:下边界 lowBorder 和上边界 highBorder。开始时目标可能是 1 到 999 中任意一个,所以把边界设在范围之外——lowBorder = 0、highBorder = 1000(即 lowestPossible-1 与 highestPossible+1)。每次猜完,就把其中一个边界往中间挪,范围随之缩小。
# 初始化:把边界设在可能范围的两侧之外 lowestPossible = 1 highestPossible = 999 lowBorder = lowestPossible - 1 # 0,比最小值还小 highBorder = highestPossible + 1 # 1000,比最大值还大
lowBorder 设成 0(而非 1)、highBorder 设成 1000(而非 999),是为了让取中点公式能覆盖到 1 和 999 这两个端点值。如果边界正好卡在 1 和 999,那么算出的中点就永远碰不到端点,万一答案是 1 或 999 就会漏掉。
每一次都猜当前区间的正中间,才能保证无论目标偏大还是偏小,被排除掉的都恰好是一半。中点的算法是"下边界加上区间长度的一半":guess = int(lowBorder + (highBorder - lowBorder) / 2)。用 int() 是因为猜测必须是整数,而除以 2 可能得到小数。
lowBorder + (highBorder - lowBorder) / 2 和 (lowBorder + highBorder) / 2 数学上相等,但前者写法更稳健——在处理超大数字时能避免两数相加溢出。养成这个习惯,将来处理大规模数据时会受益。
谜语者不直接说话,而是每次召唤一只怪物来暗示。规则是:如果出现的是侦察兵(scout),说明真正的数字比你猜的更大(greater);如果出现的是Munchkin 食人魔,说明真正的数字比你猜的更小。你要先用 enemy.type 读出怪物类型,再据此判断该往哪个方向调整。
| 出现的怪物 | 含义 | 目标数字 | 下一步 |
|---|---|---|---|
| "scout"(侦察兵) | 猜小了 | 比 guess 更大 | 抬高下边界 |
| "munchkin"(食人魔) | 猜大了 | 比 guess 更小 | 压低上边界 |
读出怪物类型后,用 if / elif 分两种情况处理。侦察兵表示目标更大,那说明"我们猜的这个数太小了",可以把它作为新的下边界;Munchkin 表示目标更小,说明"这个数太大了",把它作为新的上边界。
# 根据怪物类型判断猜大了还是猜小了 if enemy.type == "scout": # 目标更大 → 猜小了 lowBorder = guess # 抬高下边界 elif enemy.type == "munchkin": # 目标更小 → 猜大了 highBorder = guess # 压低上边界
调整区间的关键动作,就是把"这次的猜测 guess"赋值给某个边界。猜小了(scout)就 lowBorder = guess,意思是"目标一定大于 guess,所以下界抬到这里";猜大了(munchkin)就 highBorder = guess,意思是"目标一定小于 guess,上界压到这里"。这样每次都把不可能的那一半永久排除。
每一轮"猜中点 → 看怪物 → 移边界"都会让区间缩小一半。随着 lowBorder 和 highBorder 越来越接近,中点也越来越逼近真正的数字。当区间收缩到只剩一个可能值时,那个中点就正是谜语者藏的数字——命中,胜利!
每次减半,意味着 N 个数字最多需要 log₂(N) 次就能锁定。999 个数字,log₂(999) ≈ 9.96,向上取整就是 10 次——这正是关卡给你 10 次机会的原因,不多不少。这种"次数随规模只对数级增长"的特性,让二分查找即便面对海量数据也快得惊人。
| 数字范围 N | 线性查找最坏次数 | 二分查找最坏次数 |
|---|---|---|
| 10 | 10 | 4 |
| 1000 | 1000 | 10 |
| 1000000 | 1000000 | 20 |
| 10 亿 | 10 亿 | 约 30 |
git bisect 定位引入 bug 的提交、甚至调试时"注释掉一半代码看问题还在不在"——本质上都是二分查找。它是计算机科学里应用最广的思想之一。
关卡有个特别的节奏:每次猜测召唤的怪物,必须先被击败,你才能进行下一次猜测。所以主循环里要判断——如果场上有敌人,就先根据它的类型调整边界、再攻击它;如果没有敌人,才计算新中点并说出猜测。这样"猜一次 → 打一次"交替进行,直到猜中。
这一节聚焦关卡的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
谜语者臭名昭著地爱撒谎,但他无法违背"更大/更小"这条规则——这就是我们的突破口。策略只有一个:永远猜当前区间的正中间。每次猜测都恰好排除一半可能,10 次减半足以从 999 个数字里锁定唯一答案。这是一场"用算法碾压运气"的博弈。
先设定搜索范围。可能值是 1~999,但边界要设在范围之外:lowBorder = lowestPossible - 1(0)、highBorder = highestPossible + 1(1000)。这样取中点时才能覆盖到 1 和 999 两个端点,不会因边界卡死而漏掉答案。
# 关键片段:把边界设在可能范围两侧之外 lowestPossible = 1 highestPossible = 999 lowBorder = lowestPossible - 1 # 0 highBorder = highestPossible + 1 # 1000
主循环每轮先看场上有没有怪物。若有,说明这是对上一次猜测的反馈:读 enemy.type,是 scout 就抬 lowBorder、是 munchkin 就压 highBorder,然后 hero.attack 击败它腾出下一次猜测的机会。移边界必须在攻击前或攻击时完成,否则怪物消失后就读不到暗示了。
# 关键片段:根据怪物类型调整区间,再清场 enemy = hero.findNearest(hero.findEnemies()) if enemy: if enemy.type == "scout": # 目标更大 → 猜小了 lowBorder = guess elif enemy.type == "munchkin": # 目标更小 → 猜大了 highBorder = guess hero.attack(enemy) # 击败后才能继续猜
当场上清空、没有怪物时,就该发起新一轮猜测:用取中点公式算出当前区间正中间的整数 guess,再 hero.say(guess) 把它喊给谜语者。喊完谜语者会召唤新怪物暗示——循环回到上一步继续收缩区间。
# 关键片段:没敌人时,猜当前区间的正中间 else: guess = int(lowBorder + (highBorder - lowBorder) / 2) hero.say(guess)
guess,而 guess 还没被赋过值,程序会报错。实际关卡里第一帧通常没有敌人,会先走 else 分支算出首个 guess;但严谨起见,可在循环外给 guess 一个初始中点值。
int(...) 转成整数,否则猜测带小数,谜语者无法匹配。lowestPossible-1 和 highestPossible+1;若正好卡在 1 和 999,答案是端点时会漏掉。enemy.type 调整边界,再 attack。guess 可能报错,注意让它先在 else 分支取到值。lowBorder = guess 和 highBorder = guess 对调,会导致区间朝反方向"扩张"。恭喜你破解了谜语者的骗局!在封神冰川的这一单元,你掌握了计算机科学中最经典的算法之一——二分查找。你理解了它"每次猜正中间、据反馈砍掉一半"的核心思想;学会用 lowBorder 与 highBorder 圈定搜索区间、用取中点公式 int(lowBorder + (highBorder - lowBorder) / 2) 计算猜测;懂得根据怪物类型判断高低(scout 更大、munchkin 更小)并相应移动边界;也明白了为什么区间对数级收缩能让 10 次机会稳赢 999 个可能。在"猜猜我的数字"一关里,你把这套"折半逼近"的算法演绎得淋漓尽致。来回顾一下核心概念:
| 核心概念 | 关键点 | 对应写法 |
|---|---|---|
| 初始化边界 | 设在可能范围之外 | low=lowest-1, high=highest+1 |
| 取中点 | 猜当前区间正中间 | int(low + (high-low)/2) |
| 读暗示 | 看怪物类型 | enemy.type |
| scout | 目标更大,抬下界 | lowBorder = guess |
| munchkin | 目标更小,压上界 | highBorder = guess |
| 清场再猜 | 先击败怪物 | hero.attack(enemy) |
| 说出猜测 | 喊给谜语者 | hero.say(guess) |
| 对数级效率 | N 个数约 log₂N 次 | 999 → 约 10 次 |
懂得果断放弃一半可能、用最少的尝试锁定真相——这份"折半的智慧"会伴随你走遍代码世界的每一座高峰。
封神冰川的谜题已被你逐一破解,向着算法之巅,继续前行!🧊❄️🔍
扣哒世界 编程知识 · 计算机科学 6(封神冰川):第七单元
扣哒世界教研团队