🧠 第三章 编程语言
📄 一句话概括

目标 73,二分策略六次猜中:50, 75, 62, 68, 71, 73。 一千局下来随机猜平均 7.58 次、最坏 17 次; 二分平均 5.83 次、最坏 7 次——正好等于理论上界。

📝 本节和前面几节的关系

上一节练排版,这一节用循环加条件写一个小游戏。 它看着是玩,实际上讲的是「一次提问能排除多少可能」。

一、游戏规则和循环骨架

规则:心里想一个 1 到 100 的整数,对方每次猜一个,你只回答「大了」「小了」或「中了」。 代码骨架是一个 while True:猜、比、缩小范围、猜中就 break。 范围用两个变量 lo 和 hi 表示,猜小了就 lo = g + 1,猜大了就 hi = g - 1。 +1 和 -1 很关键:猜过的那个数已经排除了,再留在范围里就是浪费。 漏掉这个 +1,最坏情况会多猜一次。 Demo 跑了一局并打印全部过程:目标 73,猜的是 50, 75, 62, 68, 71, 73,六次命中。

二、二分:每次砍一半

二分策略就是每次猜范围的中间值:(lo + hi) // 2。 注意这里必须用整除,用 / 会得到浮点,没法当下标也没法比较。 每猜一次,候选范围至少减半。100 个数最多需要 log₂100 上取整,也就是 7 次。 Demo 一千局的最坏结果正好是 7 次——理论和实测对上了。 平均 5.83 次,比 7 小,因为有的数运气好一次就中,大多数落在 6 到 7 次。

三、随机猜:也在缩范围,但会白猜

随机策略每次在 [lo, hi] 里随便挑一个。它也在缩范围——因为猜完还是按大小调整边界。 但它可能连着猜同一片区域,等于白问。所以平均 7.58 次、最坏 17 次,都明显差于二分。 差距有多大?平均多 1.75 次,最坏多 10 次。在 100 个数的小游戏里,这只是多点几下鼠标; 换成「问专家一个问题要 5 块钱」,就是成本差一倍。 把范围放大到 1 到 1000,差距更明显:随机平均 12.06 次、最坏 23 次; 二分平均 8.82 次、最坏 10 次,正好等于 log₂1000 上取整。

四、这件事跟数据标注的关系

猜数字是「二十问」游戏的最小版本:每次提问,你要让剩下的可能性尽量少。 标注规范里那些「请选择最接近的一项」,本质上也是在设计提问。 选项切得粗,一个问题排除不了多少可能,标注员就得反复问;选项切得细,一个问题顶三个。 所以「二分」这个思路在标注里到处都是:置信度分档、难例分层抽检、 按类别逐层细分——都是让每一步砍掉一半。 这一节的小游戏,练的就是这个直觉:好问题的标准是「能排除多少」,不是「问得多详细」。

五、动手 Demo:随机策略与二分策略各玩一千局

先跑一局二分策略并打印每一次猜的数; 再让随机策略与二分策略在 1 到 100 上各玩 1000 局,统计平均、最坏、最好次数; 然后把范围放大到 1 到 1000,各玩 300 局再对比一次,并与理论上界对照。

def play(guess_fn, target):
    lo, hi, n, trail = 1, 100, 0, []
    while True:
        g = guess_fn(lo, hi)
        n += 1
        trail.append(g)
        if g == target:
            return n, trail
        if g < target:
            lo = g + 1
        else:
            hi = g - 1

def half_g(lo, hi):
    return (lo + hi) // 2      # 必须整除

实跑输出:

--------------------------------------------------------------------
实例 5:猜数字,随机策略与二分策略各玩一千局
--------------------------------------------------------------------
  目标数(先告诉你看得清楚) 73
  二分策略的每一猜         [50, 75, 62, 68, 71, 73]
  猜中用了                 6 次

  策略     平均次数   最坏   最好
  ------------------------------
  随机猜       7.58     17      1
  二分         5.83      7      1

  二分理论上界             7 次(2^7 = 128 ≥ 100)
  随机策略也在缩范围,但它可能连着猜同一片,白猜。
  二分每猜一次,候选范围必砍一半;随机可能连着猜中同一片区域。
  这就是「有信息量的提问」和「瞎问」的差别——
  数据标注里问专家选哪类,也是同一个道理。

  换个范围再验一次(1~1000):
  策略(1~1000)   平均次数   最坏
  -------------------------------
  随机猜              12.06     23
  二分                 8.82     10
  二分理论上界             10 次(2^10 = 1024 ≥ 1000)

一局记录很清楚:猜 50(小了)、75(大了)、62、68、71、73,六次命中。 每一次都把范围砍掉一半。 一千局统计:随机 7.58 平均、17 最坏、1 最好; 二分 5.83 平均、7 最坏、1 最好。 二分的最坏 7 次正好等于 log₂100 上取整——理论上界就是这么来的,实测一模一样。 放大到 1 到 1000:随机 12.06 平均、23 最坏; 二分 8.82 平均、10 最坏,等于 log₂1000 上取整。

30 秒实验

  1. 把范围改成 1 到 10000,先算出二分的最坏次数,再跑一遍验证。
  2. 加第三种策略:每次猜 lo + (hi - lo) // 3,看平均次数比二分的多还是少。
  3. 想一想:为什么随机策略的最坏次数会比二分多出一倍以上?

常见误区

  1. 「随机猜和二分差不多」——平均多 1.75 次,最坏多 10 次。
  2. 「范围调整不用 +1」——猜过的数已经排除,留着就多猜一次。
  3. 「问题问得越细越好」——好问题的标准是能排除多少可能,不是多详细。
自测三问(点开对答案) 1. 1 到 100 用二分最多猜几次?为什么? —— 7 次;2 的 7 次方是 128,已经覆盖 100 个数。 2. 为什么边界要写成 `lo = g + 1`? —— 猜过的那个数已经排除,留在范围里会多猜一次。 3. 这个游戏跟标注规范有什么关系? —— 都在设计「提问」,好问题能一次排除掉大部分可能性。

第三章的语法和实例到此结束,下一章进机器学习算法——从「模型怎么学」开始。

相关笔记