欢迎来到计算机科学 5 的第十八单元!这一单元你将潜入计算机最底层的语言——数制。我们平时数数用的是十进制,但计算机的世界建立在二进制之上。在关卡"形状和性状"里,你要命令魔法机器人处决食人魔,而机器人只听得懂二进制和三进制的命令。于是你必须写出一套算法,把十进制数字翻译成机器人能理解的进制字符串。这背后的核心,是计算机科学中最经典的算法之一:除基取余(divide and take remainder)。
N 进制的通用算法——用 while 循环不断把数字除以基数、取余数,并把每次的余数拼在结果字符串的前面,直到数字变成 0 为止。除以 2 就得到二进制,除以 3 就得到三进制——换个除数,就换个进制。
关卡说明讲得很生动:我们日常用的是十进制(decimal),dec 表示 10,就像 decade(十年)。此外还有二进制(binary,bi = 2)和三进制(trinary,tri = 3)。每种数字系统的"底数"就叫基数(Base):十进制是 Base 10,二进制是 Base 2。基数决定了这个系统里一共有几种数字符号:
| 数字系统 | 基数 | 可用的数字符号 |
|---|---|---|
| 二进制 Binary | Base 2 | 0, 1 |
| 三进制 Trinary | Base 3 | 0, 1, 2 |
| 十进制 Decimal | Base 10 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 |
| 十六进制 Hex | Base 16 | 0~9, a, b, c, d, e, f |
10。同理三进制里够不着 3,十进制里够不着 10。这就是为什么每个系统的数字符号最大只到"基数减一"。
同一个"量",在不同进制里写法完全不同。关卡用数字 9 做了绝妙的演示——它在十进制是 9,但在三进制变成 100,在二进制变成 1001。数量没变,只是"记数的规则"变了。
把十进制数转成别的进制,核心方法是:用想要的基数作为除数,反复相除,并记下每一步的余数。关卡原话是:"将一个十进制数字转换成另一个进制的方法是用想要的基数作为除数,并观察余数。"每次相除产生一个余数(这一位的数字)和一个商(下一轮要继续处理的数),如此循环,直到商变成 0。
跟着关卡的例子手算一遍,算法就一目了然了。转换时不断除以基数、记录余数,直到商为 0;最后把所有余数从下往上(从最后到最先)读,就是答案。
9 ÷ 3 = 3 ... 余 0 3 ÷ 3 = 1 ... 余 0 1 ÷ 3 = 0 ... 余 1 # 从下往上读:100 ✓
9 ÷ 2 = 4 ... 余 1 4 ÷ 2 = 2 ... 余 0 2 ÷ 2 = 1 ... 余 0 1 ÷ 2 = 0 ... 余 1 # 从下往上读:1001 ✓
手算里"反复相除直到商为 0"这个过程,用代码写就是一个 while 循环。循环条件是 while number != 0——只要 number 还没被除到 0,就继续处理下一位。每循环一次就处理一位数字,当 number 终于变成 0,说明所有位都处理完了,循环自然结束。
def toTernary(number): string = "" # 从空字符串开始 while number != 0: # 只要还没除到 0,就继续 # ...取余、更新 number、拼字符串... pass return string
循环体里做两件算术:先用取余运算符 % 算出这一位的数字 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,干净利落。这一步和取余是配套的——余数管"当前位",商管"剩下的部分"。
这是整个算法的点睛之笔,也呼应了"余数要倒着读"的规律。每算出一个余数,就把它拼在已有字符串的前面:string = remainder + string。因为最先算出的是最低位、最后算出的是最高位,所以后来的余数要不断"挤到前面去",最终字符串的顺序就自然正确了。
# 把新余数拼在字符串的【前面】(不是后面!) string = remainder + string
string = string + remainder(拼在后面),结果的位序会完全颠倒。9 的二进制会变成错误的 1001 的逆序。所以务必记住是 remainder + string——新的在前。
除基取余算法最优雅的地方在于它的通用性:转成几进制,就把除数改成几。toTernary(三进制)用 % 3 和 / 3;toBinary(二进制)只需把它们全换成 % 2 和 / 2,其余逻辑一字不差。关卡也提示:"再次重复这些步骤:获取余数、减去余数和追加字符串。记住二进制用另一种方式表示 2!"——这里"另一种方式"就是指把基数从 3 改成 2。
while number != 0: remainder = number % 3 number = (number-remainder)/3 string = remainder + string
while number != 0: remainder = number % 2 number = (number-remainder)/2 string = remainder + string
2 或 3 换成任意基数 n,这套算法就能转成 n 进制。这正是好算法的魅力——抓住本质、举一反三。(当基数超过 10 时,还需要把 10、11…映射成 a、b… 等字母,这是十六进制的额外一步。)
这一节聚焦关卡的策略、思路与算法设计,只展示用于说明的关键片段,完整通关程序请你自己动手完成。
本关是本单元唯一的关卡,也是进制转换的综合实战。魔法机器人步行者能替你处决食人魔,但它只接受进制字符串命令:命令由两部分组成——敌人血量的三进制表示,加上敌人类型的二进制表示。策略是:持续找出场上最危险的敌人(血量最高者),把它的血量和类型分别转换成对应进制,拼成命令字符串说给机器人听。
算法有三块。第一,补全 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))
toTernary 整段逻辑复制过来,只把两个基数 3 改成 2。这也印证了除基取余算法的通用性——同一个骨架,改个除数就适配新进制。
mostDangerous = None、mostHealth = 0),遍历每个敌人,谁的血量比当前记录更高,就把它换上擂台。遍历结束,擂台上的就是血量最高、最危险的敌人。
toTernary 和 toBinary 用错了对象(比如给血量用二进制),机器人就会误解命令、攻击错误的目标。
remainder + string(新的在前),写成 string + remainder 会让结果位序完全颠倒。while number != 0;写成 while number > 1 之类会漏掉最后一位。% 2 和 / 2,三进制用 % 3 和 / 3,改了取余却忘了改整除会出错。(number - remainder) / base,直接 number / base 可能得到小数、破坏迭代。return string,否则返回 None,机器人收不到命令。string = "",否则拼接时会报错。恭喜你掌握了计算机最底层的语言——数制转换!这一单元你理解了"基数"的含义:每种数字系统能用的符号个数等于它的基数,且永远够不着基数本身。你学会了经典的除基取余算法:用 while 循环不断把数字除以基数、取余数,用 (number - remainder) / base 迭代更新,并把每个余数拼在字符串前面(string = remainder + string),直到数字变成 0。你还明白了这套算法的通用性——除以 2 得二进制、除以 3 得三进制,换个除数就换个进制。在"形状和性状"关卡里,你把这套算法用于让魔法机器人听懂命令。来回顾一下核心概念:
| 核心概念 | 关键点 | 说明 |
|---|---|---|
| 基数 Base | 符号个数 = 基数,够不着基数本身 | 二进制只有 0、1 |
| 多进制表示 | 同一个数,各进制写法不同 | 9 = 100(3) = 1001(2) |
| 除基取余 | 反复除以基数、记录余数 | 算法核心 |
| while 到 0 | while number != 0 | 处理完所有位 |
| 取余得当前位 | remainder = number % base | 这一位的数字 |
| 整除得下一轮 | (number-remainder)/base | 剩下的部分 |
| 字符串前置拼接 | string = remainder + string | 余数倒序读 |
| 换除数换进制 | 2→二进制,3→三进制 | 算法通用 |
懂得在十进制与二进制、三进制之间自如穿梭,看透数字背后记数规则的英雄,才是云峰之上真正通晓机器语言的程序员。
继续向雪山之巅挺进吧,勇敢的程序员!🏔️🤖🔢
扣哒世界 编程知识 · 计算机科学 5(安息云峰):第十八单元
扣哒世界教研团队