有三根杆子 A,B,C。A 杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:
- 每次只能移动一个圆盘;
- 大盘不能叠在小盘上面。
提示:可将圆盘临时置于 B 杆,也可将从 A 杆移出的圆盘重新移回 A 杆,但都必须遵循上述两条规则。
问:如何移?最少要移动多少次?
汉诺塔是经典的数学问题,“如何移”的问题也是迭代教学的必考问题。但是以迭代解决其的原理多少有点糊里糊涂。
1 | _ _ _ |
示例代码:
1 | // Tower of Hanoi 汉诺塔 |
迭代
函数迭代的基本思想就是将大问题逐步简化为易于解决的小问题,再从小问题一步一步逆推出整个问题的答案。比如下面阶乘的示例就是将数值一步一步缩小,直至变成易求出阶乘的 1 这个数:
1 | long factorial(int val){ |
整体思想
因此,汉诺塔的题解也不应该过度关注每一步的走法,而应该从整体来看,将问题简化成两块圆盘(未在 C 柱上的最大圆盘,其余所有更小圆盘结成的组合体)的问题(其中,目标柱指前文字符画中的的柱 C,而在首次循环之前源柱代表柱 A,柱 B 是中转柱):
1 | graph TB |
由此循环,不断将最大的圆盘放到目标柱上,直到源柱只剩一个圆盘。
而问题中,只有最后只剩一个圆盘没有被放上目标柱时状况是可预见、可计算的:“将此圆盘移动至目标柱”。因此就要以这一步为基推出之后的结果。
迭代体
因而,迭代体中 else 块的首个语句 ToH(len - 1, A, C, B); 的意义即是“将 A 柱(源柱)上所有的块都放入 B,只留最大的一块”[1];第二句 printf("[%d] Moved: %c -> %c\n",len , A, C); 将最大的一块放到了 C 柱上;第三句 ToH(len - 1, B, A, C); 即是交换了源柱和中转柱的指向的实际柱(地址)并继续循环。
变量循环到最小值 1 时,出现了稳定结果,整个函数借此也可以完成迭代。
- 1.这一语句能够成立的前提就是“小圆盘可以放在大圆盘上”。由于目标柱最上的圆盘总是比其他两柱上的所有圆盘大,所以可以将其用作中转。 ↩