汉诺塔
汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金钢石柱子,其中一根柱子自底向上叠着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
——引自维基百科
若给汉诺塔传说中三根柱子分别用英文字母a,b,c命名,其中只有a柱子摆放n片圆盘(1<=n<=100000), 若要把a柱子上的所有圆盘转移到c柱子上,问最少需要移动多少次圆盘。
移动圆盘的规则如下:
- 每次只能移动一片圆盘
- 直径大的圆盘必须摆放在直径小的圆盘之上
递归求解
汉诺塔问题通过简单的递归进行求解,代码比较简洁,通俗易懂。其实汉诺塔问题的移动次数是有规律可寻的,通过递归代码找出相应的规律,并通过数学方法得到结果效率才是最高的。
- 当n=1时,a柱子只有一个圆盘,直接移至c柱
- 当n>1时,根据规则1和2,将a柱子n-1个圆盘移动到b柱子,然后将a剩下的一个圆盘移动到c,接着再把b上暂时放着的n-1个圆盘移动到c
递归求解其实就是不断降低问题规模的过程,将b柱子的n-1个圆盘移至c何尝不重复上述两点的过程。递归求解的C代码如图3-1所示。
void Hanoi(int n, char a, char b, char c) { if(n == 1) { Move(a, c); } else { Hanoi(n-1, a, c, b); /*将a柱子n-1个圆盘移动到b柱子*/ Move(a, c); /*将a剩下的一个圆盘移动到c*/ Hanoi(n-1, b, a, c); /*再把b上暂时放着的n-1个圆盘移动到c*/ } } void Move(char a, char b) { printf("Move 1 disk: %c ---------> %c\n", a, b); }
图3-1 汉诺塔递归求解代码
如图3-1的代码可以得出汉诺塔移动圆盘次数的递推关系:
- Hanoi(n) = 1 , n =1 ;
- Hanoi(n) = 2 * Hanoi(n-1) + 1, n>1;
令b(n) = Hanoi(n)+1,可以得出b(n)是一个以2为底,公比为2的等比数列。由此可得,
b(n) = 2n, n>0
Hanoi(n) = 2n - 1, n>0
增加约束条件的汉诺塔
Q: 若加上一个限制条件,圆盘只能在相邻柱子之间移动,又如何解决?假设a, b, c并排,b在中间,即a, c不相邻,把a上的一个圆盘移动到c上必须先移至b,然后再移动到c。
同理使用递归求解,根据原有规则和新增约束条件,推导出移动圆盘的次数的规律。
- 当n=1时,a柱子只有一个圆盘,先移到b,再移至c
- 当n>1时,先将a柱的n-1个圆盘通过b柱移至c柱,再将a柱子剩下的一个圆盘移到b; 接着把c柱n-1个圆盘通过b柱移到a; 然后把b柱子目前唯一的圆盘移至c,最后把a柱子的n-1个圆盘通过b移至c
递归代码如图3-2所示。
void Hanoi(int n, char a, char b, char c) { if(n==1) { //a柱子只有一个圆盘,先移到b,再移至c Move(a, b); Move(b, c); } else { Hanoi(n-1, a, b, c); //先将a柱的n-1个圆盘通过b柱移至c柱 Move(a, b); //a柱子剩下的一个圆盘移到b Hanoi(n-1, c, b, a); //把c柱n-1个圆盘通过b柱移到a Move(b, c); //把b柱子目前唯一的圆盘移至c Hanoi(n-1, a, b ,c); //把a柱子的n-1个圆盘通过b移至c } } void Move(char a, char b) { printf("%c --> %c\n", a, b); }
图3-2 增加约束条件的汉诺塔递归代码
由图3-2可得,移动圆盘次数与圆盘个数的递推关系,
- 当n=1时,Hanoi(n) = 2;
- 当n>1时,Hanoi(n) = 3 * Hanoi(n-1) + 2;
最后求得:
Hanoi(n) = 3n - 1, n>0
这道题目的结果可能会很大,输出的结果要 mod 1,000,000,007。
题目的输出提示让我很疑惑,结果很大和 mod 这个数有什么关系。百度一下,貌似与什么费马小定理有关。但是我还是看不懂,先留着再进行研究学习。