去做题

第十八单元:进制转换 · 除基取余算法

Unit 18: Number Bases — The Divide-and-Remainder Algorithm
计算机科学 5 · 安息云峰 Cloudrip Mountain · 形状和性状

18.1 什么是进制

What Is a Number Base

欢迎来到计算机科学 5 的第十八单元!这一单元你将潜入计算机最底层的语言——数制。我们平时数数用的是十进制,但计算机的世界建立在二进制之上。在关卡"形状和性状"里,你要命令魔法机器人处决食人魔,而机器人只听得懂二进制和三进制的命令。于是你必须写出一套算法,把十进制数字翻译成机器人能理解的进制字符串。这背后的核心,是计算机科学中最经典的算法之一:除基取余(divide and take remainder)。

本单元主线:把十进制数转成 N 进制的通用算法——用 while 循环不断把数字除以基数、取余数,并把每次的余数拼在结果字符串的前面,直到数字变成 0 为止。除以 2 就得到二进制,除以 3 就得到三进制——换个除数,就换个进制。

18.1.1 基数与数字系统(Base and Number Systems)

18.1.1.1 "基数"决定了有几个数字符号 The base determines how many digits exist

关卡说明讲得很生动:我们日常用的是十进制(decimal),dec 表示 10,就像 decade(十年)。此外还有二进制(binary,bi = 2)和三进制(trinary,tri = 3)。每种数字系统的"底数"就叫基数(Base):十进制是 Base 10,二进制是 Base 2。基数决定了这个系统里一共有几种数字符号:

数字系统基数可用的数字符号
二进制 BinaryBase 20, 1
三进制 TrinaryBase 30, 1, 2
十进制 DecimalBase 100, 1, 2, 3, 4, 5, 6, 7, 8, 9
十六进制 HexBase 160~9, a, b, c, d, e, f
🔢 关键直觉:你永远"够不着"基数本身
在二进制里,你数数时永远达不到数字 2——数到 1 之后,下一个就要进位成 10。同理三进制里够不着 3,十进制里够不着 10。这就是为什么每个系统的数字符号最大只到"基数减一"。

18.1.2 同一个数的多种写法(One Number, Many Notations)

18.1.2.1 数字 9 在各进制里长得都不一样 The number 9 looks different in each base

同一个"量",在不同进制里写法完全不同。关卡用数字 9 做了绝妙的演示——它在十进制是 9,但在三进制变成 100,在二进制变成 1001。数量没变,只是"记数的规则"变了。

同一个数 9,在不同进制里的写法 Base 10(十进制) 9 Base 3(三进制) 100 Base 2(二进制) 1001 数量相同,只是记数规则不同
进制越小,位数越长:注意到了吗——基数越小,同一个数需要的位数就越多。9 在十进制只要 1 位,二进制却要 4 位。因为每一位能表示的"信息"更少,就得用更多位来凑。这也是为什么二进制数字串往往很长。

18.2 除基取余算法

The Divide-and-Remainder Algorithm

18.2.1 除法与余数(Division and Remainder)

18.2.1.1 用目标基数当除数,反复相除 Divide repeatedly by the target base

把十进制数转成别的进制,核心方法是:用想要的基数作为除数,反复相除,并记下每一步的余数。关卡原话是:"将一个十进制数字转换成另一个进制的方法是用想要的基数作为除数,并观察余数。"每次相除产生一个余数(这一位的数字)和一个商(下一轮要继续处理的数),如此循环,直到商变成 0。

每一步:取余数(这一位)+ 求商(继续处理) number % base → remainder 被转换的数 除数=基数 当前这一位

18.2.2 手算示例(Working It Out by Hand)

18.2.2.1 把 9 转成三进制和二进制 Converting 9 to base 3 and base 2

跟着关卡的例子手算一遍,算法就一目了然了。转换时不断除以基数、记录余数,直到商为 0;最后把所有余数从下往上(从最后到最先)读,就是答案。

9 → 三进制(除以 3)
9 ÷ 3 = 3 ... 余 0
3 ÷ 3 = 1 ... 余 0
1 ÷ 3 = 0 ... 余 1

# 从下往上读:100 ✓
9 → 二进制(除以 2)
9 ÷ 2 = 4 ... 余 1
4 ÷ 2 = 2 ... 余 0
2 ÷ 2 = 1 ... 余 0
1 ÷ 2 = 0 ... 余 1

# 从下往上读:1001 ✓
余数要"倒着"读:这是本算法最容易出错的地方。最先算出的余数是结果的最低位(最右边),最后算出的余数才是最高位(最左边)。所以读答案时要从最后一个余数往回读——这正是稍后代码里"把新余数拼在字符串前面"的原因。

18.3 用代码实现转换

Implementing the Conversion in Code

18.3.1 while 迭代到商为 0(Loop Until Zero)

18.3.1.1 只要 number 不是 0,就继续 Keep going while number is not zero

手算里"反复相除直到商为 0"这个过程,用代码写就是一个 while 循环。循环条件是 while number != 0——只要 number 还没被除到 0,就继续处理下一位。每循环一次就处理一位数字,当 number 终于变成 0,说明所有位都处理完了,循环自然结束。

def toTernary(number):
    string = ""              # 从空字符串开始
    while number != 0:       # 只要还没除到 0,就继续
        # ...取余、更新 number、拼字符串...
        pass
    return string

18.3.2 取余与更新 number(Remainder and Update)

18.3.2.1 取余得到这一位,整除得到下一轮 Remainder gives a digit; division gives the next number

循环体里做两件算术:先用取余运算符 % 算出这一位的数字 remainder = number % 3;再把 number 更新为它除以基数的商,准备下一轮。关卡用了一个巧妙的写法:number = (number - remainder) / 3——先减掉余数让它能整除,再除以基数,确保得到的是干净的整数商。

# 1. 取余数:这一位的数字
remainder = number % 3

# 2. 更新 number:减掉余数后除以基数(得到商)
number = (number - remainder) / 3
为什么先减余数:(number - remainder) 保证结果一定能被基数整除,避免出现小数。比如 9 - 0 = 9,9 / 3 = 3,干净利落。这一步和取余是配套的——余数管"当前位",商管"剩下的部分"。

18.3.3 字符串前置拼接(Prepending to the String)

18.3.3.1 新余数拼在旧字符串"前面" The new digit goes in front

这是整个算法的点睛之笔,也呼应了"余数要倒着读"的规律。每算出一个余数,就把它拼在已有字符串的前面:string = remainder + string。因为最先算出的是最低位、最后算出的是最高位,所以后来的余数要不断"挤到前面去",最终字符串的顺序就自然正确了。

# 把新余数拼在字符串的【前面】(不是后面!)
string = remainder + string
9 转二进制:新余数不断拼到前面 余1 → 1 string = "1" 余0 → 0 1 string = "01" 余0 → 0 01 string = "001" 余1 → 1 001 = "1001" ✓
拼后面就全反了:如果误写成 string = string + remainder(拼在后面),结果的位序会完全颠倒。9 的二进制会变成错误的 1001 的逆序。所以务必记住是 remainder + string——新的在前。

18.4 二进制与三进制

Binary and Trinary

18.4.1 换个除数就换个进制(Change the Divisor)

18.4.1.1 同一套算法,只改基数 Same algorithm, just a different base

除基取余算法最优雅的地方在于它的通用性:转成几进制,就把除数改成几。toTernary(三进制)用 % 3 和 / 3;toBinary(二进制)只需把它们全换成 % 2 和 / 2,其余逻辑一字不差。关卡也提示:"再次重复这些步骤:获取余数、减去余数和追加字符串。记住二进制用另一种方式表示 2!"——这里"另一种方式"就是指把基数从 3 改成 2。

toTernary(除以 3)
while number != 0:
    remainder = number % 3
    number = (number-remainder)/3
    string = remainder + string
toBinary(除以 2)
while number != 0:
    remainder = number % 2
    number = (number-remainder)/2
    string = remainder + string
一个模式,无限进制:只要把 2 或 3 换成任意基数 n,这套算法就能转成 n 进制。这正是好算法的魅力——抓住本质、举一反三。(当基数超过 10 时,还需要把 10、11…映射成 a、b… 等字母,这是十六进制的额外一步。)

18.5 实战关卡精讲

Level Strategy & Analysis

这一节聚焦关卡的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。

关卡 · 形状和性状

🤖 形状和性状 —— 让机器人听懂进制命令

扣哒世界 形状和性状关卡 - koudashijie.com
图2:形状和性状——找出最危险的食人魔,把它的血量转成三进制、类型转成二进制,说给机器人步行者听

策略:把敌人属性翻译成机器人的语言

本关是本单元唯一的关卡,也是进制转换的综合实战。魔法机器人步行者能替你处决食人魔,但它只接受进制字符串命令:命令由两部分组成——敌人血量的三进制表示,加上敌人类型的二进制表示。策略是:持续找出场上最危险的敌人(血量最高者),把它的血量和类型分别转换成对应进制,拼成命令字符串说给机器人听。

思路拆解

算法有三块。第一,补全 toBinary 函数:关卡已给好三进制版 toTernary,你要照着写二进制版——逻辑完全相同,只把 % 3 改成 % 2、/ 3 改成 / 2。第二,找最危险的敌人:辅助函数 findMostDangerous 遍历所有敌人,记录血量最高的那一个(血量越多越强)。第三,在主循环里下命令:把 toTernary(血量) 和 toBinary(类型) 拼接成机器人认得的格式,用 hero.say(...) 说出来。

# 关键片段一:补全 toBinary(除数从 3 改成 2)
def toBinary(number):
    string = ""
    while number != 0:
        remainder = number % 2
        number = (number - remainder) / 2
        string = remainder + string
    return string
# 关键片段二:找到最危险者,拼接进制命令
while True:
    enemies = hero.findEnemies()
    dangerous = findMostDangerous(enemies)
    if dangerous:
        # 命令格式:三进制(血量) + " " + 二进制(类型)
        hero.say(toTernary(dangerous.health) + " " + toBinary(dangerous.type))
toBinary 是 toTernary 的"复制改数":本关最省力的做法,就是把已给好的 toTernary 整段逻辑复制过来,只把两个基数 3 改成 2。这也印证了除基取余算法的通用性——同一个骨架,改个除数就适配新进制。
findMostDangerous 的思路:这个辅助函数用了经典的"打擂台"求最值算法:先假设没有最危险者(mostDangerous = None、mostHealth = 0),遍历每个敌人,谁的血量比当前记录更高,就把它换上擂台。遍历结束,擂台上的就是血量最高、最危险的敌人。
两部分命令别弄反:命令的顺序是"血量三进制在前、类型二进制在后",中间用空格分隔。如果把 toTernary 和 toBinary 用错了对象(比如给血量用二进制),机器人就会误解命令、攻击错误的目标。

18.6 常见错误

Common Mistakes

18.6.1 新手最容易踩的坑(Pitfalls for Beginners)

18.6.1.1 进制转换的典型问题 Typical issues with base conversion

扣哒世界 进制转换常见错误速查 - koudashijie.com
图3:字符串拼错方向、循环条件写错、除数用错、忘记 return,是本单元最常见的失误
易错点清单:
  • 字符串拼接方向反了——必须是 remainder + string(新的在前),写成 string + remainder 会让结果位序完全颠倒。
  • 循环条件写错——应是 while number != 0;写成 while number > 1 之类会漏掉最后一位。
  • 除数用错进制——二进制要用 % 2 和 / 2,三进制用 % 3 和 / 3,改了取余却忘了改整除会出错。
  • 忘记先减余数——更新 number 时用 (number - remainder) / base,直接 number / base 可能得到小数、破坏迭代。
  • 函数忘写 return——转换函数最后必须 return string,否则返回 None,机器人收不到命令。
  • string 忘记初始化——循环前要先 string = "",否则拼接时会报错。
  • 余数当数字直接用——余数拼进字符串时要能和字符串相加;类型/血量为 0 时循环一次都不进,需留意边界。
  • 血量与类型的进制搞混——血量用三进制、类型用二进制,两者不能对调。

本单元小结 ✨

恭喜你掌握了计算机最底层的语言——数制转换!这一单元你理解了"基数"的含义:每种数字系统能用的符号个数等于它的基数,且永远够不着基数本身。你学会了经典的除基取余算法:用 while 循环不断把数字除以基数、取余数,用 (number - remainder) / base 迭代更新,并把每个余数拼在字符串前面(string = remainder + string),直到数字变成 0。你还明白了这套算法的通用性——除以 2 得二进制、除以 3 得三进制,换个除数就换个进制。在"形状和性状"关卡里,你把这套算法用于让魔法机器人听懂命令。来回顾一下核心概念:

核心概念关键点说明
基数 Base符号个数 = 基数,够不着基数本身二进制只有 0、1
多进制表示同一个数,各进制写法不同9 = 100(3) = 1001(2)
除基取余反复除以基数、记录余数算法核心
while 到 0while number != 0处理完所有位
取余得当前位remainder = number % base这一位的数字
整除得下一轮(number-remainder)/base剩下的部分
字符串前置拼接string = remainder + string余数倒序读
换除数换进制2→二进制,3→三进制算法通用

懂得在十进制与二进制、三进制之间自如穿梭,看透数字背后记数规则的英雄,才是云峰之上真正通晓机器语言的程序员。
继续向雪山之巅挺进吧,勇敢的程序员!🏔️🤖🔢

扣哒世界 编程知识 · 计算机科学 5(安息云峰):第十八单元

扣哒世界教研团队