欢迎光临散文网 会员登陆 & 注册

【TIS-100 攻略】TIS-NET 第 20 关:超长序列排序器

2022-11-11 18:21 作者:ココアお姉ちゃん  | 我要投稿

本文首发于 B 站《TIS-100》文集(https://www.bilibili.com/read/readlist/rl626023)。原创不易,转载请注明出处。

TIS-NET 第 20 关《超长序列排序器》(Prolonged Sequence Sorter)关卡展示

本关会给你一个以 -1 结尾的超长序列,序列中的数字都在 0~9 范围内。你需要将序列中的数字从小到大排好输出给 OUT。

排序题我们在上一章的 22 关做过,不过那一关的序列长度短(不超过 10 个数字)、取值范围大(10~99)。对于这样的序列,我们总结出了规律,适合用选择排序或插入排序算法。但是本关的数据正好反过来,取值范围小(0~9),序列长度长(38 个数字)。如果仍然使用选择排序或插入排序的话,我们的栈只有 15 格空间,是无法完成任务的。但是因为本关数字的取值范围小,我们可以使用计数排序来完成。

如果一个序列的取值范围也大,序列长度也长的话,怎么排序呢?答案是:除非增加栈的数量,如果还是只有两个栈,那么无解。

回顾一下计数排序的过程:

第 1 步,初始化一个频数数组,它的大小等于输入量的最大值
第 2 步,读入 3,并令 3 的频数 +1
第 3 步,读入 1,并令 1 的频数 +1
第 4 步,读入 4,并令 4 的频数 +1
第 5 步,读入 1,并令 1 的频数 +1
第 6 步,读入 5,并令 5 的频数 +1
第 7 步,读入 9,并令 9 的频数 +1
第 8 步,读入 2,并令 2 的频数 +1
第 9 步,读入 6,并令 6 的频数 +1
最后一步,将每个数都输出和它的频数一致的次数:依次输出两个 1、一个 2、一个 3、一个 4、一个 5、一个 6、一个 9

以下代码假设提供的 nums 一定在 0~9 的范围内,并以 -1 结尾:

那么,我们现在根据示意图和 C 语言代码,写出本关的 TIS-100 代码:

首先看右上角的节点:

  1. 右上角的节点首先将上方栈里塞入 10 个 0,用于初始化 f 数组(mov 10 acc)

  2. (mov 0 left)

  3. (sub 1)

  4. (jnz 2)

  5. 然后进入第一阶段:每从上方收到一个 num,我们都要将 f[num] 加上 1。由于 num 从 0 开始,f[num] 是 f 数组的第 num+1 个元素,所以我们将数字 num 改写成 num+1(mov 1 acc)

  6. (add up)

  7. 并发给下方(mov acc down)。

  8. 此时检查:若 num+1 不为 0,说明尚未到达 nums 数组最后的 -1,我们跳回第 5 行,继续接收(jnz 5),直到到达最后的 -1 后,向下执行,进入第二阶段。

现在暂时把目光移动到中间靠右的节点处:

  1. 中间靠右的节点是一个纯传话节点,第一阶段里,它将收到的 num+1(mov up acc)

  2. 发给左边的节点,令其将 f[num] 加上 1(mov acc left)

  3. (jnz 1)

  4. 第二阶段里,它将上方发来的所有数字全部无脑传到右下角的节点里(mov up down)

  5. (jmp 4)

中间靠左的节点用来维护频数数组 f:

  1. 它随时等待右边传来 num+1(mov right acc)。

  2. 如果 num+1 为 0,即 num 为 -1,则跳回第 1 行,什么都不做(jez 1)。

  3. 接下来的代码就是将 f 数组的第 num+1 个元素 f[num] 加上 1,和之前《众数》题里的代码大同小异(传送门:【TIS-100 攻略】TIS-NET 第 10 关:序列众数计算器)。这里就长话短说了:上方弹栈 num+1 次后(sav)

  4. (mov up down)

  5. (sub 1)

  6. (jnz 4)

  7. 下方栈顶里的数字即为 f[num],将其(mov down acc)

  8. 加上 1 后(add 1)

  9. 放回原处(mov acc down)。

  10. 处理完成后,下方往回弹栈 num+1 次(swp)

  11. (mov down up)

  12. (sub 1)

  13. (jnz b)

以上便是第一阶段的全部工作。现在进入第二阶段,我们将目光回到上方节点。第一阶段里,上方节点的 acc 用来存储 num+1 的值,到了第二阶段,acc 就要改为存储 num 了。进入第二阶段的条件是 num+1 = 0,所以进入第二阶段后,上方节点的 acc 一定是 0,正好是第一个 num 的值。接下来:

9. 上方节点将每个 num(mov acc down)

10. 以及对应的 f[num] 都发到中间靠右的节点里(mov left down),

11. 然后判断:如果 num 到达了 9(sub 9),

12. 则跳到第 15 行执行(jez f),

13. 否则令 num 加 1(add 10),

14. 并跳回第 9 行继续发送下一组 num 和 f[num](jmp 9)。

15. 直到 num 到达 9 后,向下发送最终的 -1(mov -1 down)。

中间靠右的节点将每一组 num 和对应的 f[num] 都发到右下角的输出节点里(mov up down, jmp 4)。最后是右下角的输出节点:

  1. 右下角的节点收到 num 后(mov up acc),检查是否是负数。

  2. 是负数时,说明收到了 -1,直接跳到第 7 行输出这个 -1(jlz 7, mov acc down)。

  3. 若不是负数,则说明接下来还会收到对应的 f[num]。我们将 num 暂时存入 bak(swp),

  4. 然后从上方接收 f[num](mov up acc)。

  5. f[num] 为 0 时,表示序列中没有出现过当前的 num,直接跳回第 1 行接收下一组数据(jez 1);

  6. f[num] 不为 0 时,每输出一个 num(swp)

  7. (mov acc down)

  8. (swp)

  9. 就将 f[num] 减去 1(sub 1)。

  10. 只要 f[num] 没有减到 0,就跳回第 6 行继续输出 num(jnz 6),直到 f[num] 减到 0 后,跳回第 1 行,接收下一组数据。

点击左下角的【RUN】,稍等片刻,便会弹出结算界面:


【TIS-100 攻略】TIS-NET 第 20 关:超长序列排序器的评论 (共 条)

分享到微博请遵守国家法律