你好,我是goldsunC
让我们一起努力吧!
汉诺塔问题
公元1883年法国数学家Lucas所提出流传再印度的汉诺塔(Tower of Hanoil)游戏,是使用递归方法与堆栈概念来解决问题的典型范例。
汉诺塔游戏题目如下:
有三根木桩,第一根上面有n个盘子,最底层的盘子最大,最上层的盘子最小。汉诺塔问题就是将所有的盘子从第一根木桩开始,以第二根木桩为桥梁,全部搬到第三根木桩
。
在搬动盘子时需要遵守以下游戏规则:
- 每次只能从某个木桩的最上面移动一个盘子。
- 任何盘子可以从任何木桩搬到其它木桩。
- 直径小的盘子永远只能放在直径比其大的盘子上面。
传说在远东的一个寺庙里,僧侣们需要将一摞按照由底向上由大到小排列的64个盘子从第一根木桩移到第三根木桩,当他们刚遇到这个问题的时候,陷入了困境,不知道如何解决这个问题。它们开始互相推卸责任,最后推到了寺庙住持的身上。
然后住持对副住持说:“你只要能把前63个盘子由第一个木桩移动到第二个木桩,我就可以完成第64个盘子的移动”。然后副住持对一个僧侣说:“你只要能把前62个盘子由第一个木桩移动到第二个木桩上,我就可以完成第63个盘子的移动”。…第63个僧侣对第64个僧侣说:“你只要能把前一个盘子移动到第二个木桩,我就能完成第2个盘子的移动”。第64个僧侣很快完成了它的操作,然后第63个僧侣也很快完成了他的操作,然后第62个、61个…
盘子比较多,一步步来可能不好理解,那么咱先从一个盘子说起。
- 1个盘子:
直接从1号木桩移动到3号木桩即可,共需要移动1次。 - 2个盘子:
- step1:从1号木桩移动到2号木桩。
- step2:从1号木桩移动到3号木桩。
- step3:从2号木桩移动到3号木桩。
总共需要移动2^2-1=3次。
- 3个盘子:
- step1:从1号木桩移动到3号木桩。
- step2:从1号木桩移动到2号木桩。
- step3:从3号木桩移动到2号木桩。(这个时候把前n-1个由1号木桩移动到了2号木桩)
- step4:从1号木桩移动到3号木桩
- step5:从2号木桩移动到1号木桩。
- step6:从2号木桩移动到3号木桩。
- step4:从1号木桩移动到3号木桩。
总共需要移动23-1=7次。
依次类推,我们能够计算出当由n个盘子时,总共需要移动的次数是2n-1次。假如是64个盘子,总共需要移动264-1 = 18446744073709551615 次,这个数有多大…,简单算了一下,一秒钟如果能移动1亿次的话,总共需要移动五千八百多年,那假如只允许一天移动一次…,果真是到了世界末日啊。
这个时候相信大家都能看出来这是一个典型的递归问题,满足递归的两大特性:
- 有反复执行的过程。
- 由停止执行的出口。
OK,知道是递归,那到底该怎么递归?既然是递归,那我们不妨就倒着推应该怎么移动。
首先,如果需要把n个盘子从第一根木桩移动到第三根,那么肯定要把第一根木桩的最后一个盘子先移动到第三根木桩,而最后一个盘子又被压在了第一根木桩的最下面,那么肯定首先要把前n-1
个木桩从第一根木桩移动到第二根(中转木桩),然后再把第n个盘子从第一根木桩移动到第三根,最后把中转木桩上的n-1个盘子移动到第三根上。
哎,到这儿稍停,让我们退回一步,此时第n个盘子在第三根木桩上,前n-1个盘子在第二根木桩,试问我们能把这n-1个盘子直接从2号木桩移动到3号木桩吗?肯定是不可能的,我们还需要借助第一根和第三根,因为第三根上面是最大的那个盘子,因此我们可以简单理解为并没有盘子。
刚才我们把第n个盘子移动到了第三根木桩,这个时候需要把剩下的n-1个盘子移动到第三根木桩,那应该怎么办,对,和刚开始一样,需要把这n-1个盘子中的前n-2个移动到第一根木桩
,然后把第n-1个移动到第三根,再把剩下的n-2个移动到第三根。
上面我们分别先把第n个和第n-1个移动到了第三根木桩上,移动这两个的时候有什么不同呢?
- 移动第n个的时候,起点在第一根木桩,中转木桩是第二根,目标是第三根。
- 移动第n-1个的时候,起点是第二根木桩,中转木桩是第一根,目标是第三根。
继续往下推导的话不难理解,每当把一个木桩移动到第三根木桩(目标木桩)上的时候,另外两个木桩会更改一下自身的定位,也就是起点变中转、中转变起点。这样改变是递归调用的结果。
仔细分析下来发现,我们并不需要知道每一步是怎么移动的,我们以刚才的1,2,3号木桩分别为起点木桩,中转木桩,目标木桩,假如倒推的话,我们发现这样操作就行:
- 盘子n:将前n-1个盘子移动到中转木桩,将第n个盘子移动到目标木桩
- 盘子n-1:将前n-2个盘子移动到起点木桩,将第n-1个盘子移动到目标木桩
- 盘子n-2:将前n-3个盘子移动到中转木桩,将第n-2个盘子移动到目标木桩
- 盘子n-3:将前n-4个盘子移动到起点木桩,将第n-3个盘子移动到目标木桩
- …
- 盘子n-(n-3):将前2个盘子从
当时的起点木桩
移动到当时的中转木桩
,将第3个盘子移动到目标木桩 - 盘子n-(n-2):将第1个盘子从
当时的起点木桩
移动到当时的中转木桩
,将第2个盘子移动到目标木桩 - 盘子n-(n-1):将第一个盘子移动到目标木桩
OK,正着推不理解,倒着推算是理解了,那每一步又应该是怎么移动的呢?注意一点,怎么移动的我们无需关注!交给程序就行了!接下来上代码,分别是Python、Java和C的:
n = 1
def hanoil(level,fromWhere,temp,toWhere):
global n = 1
if level == 1:
print("Step",n,":","从",fromWhere,"移动到",toWhere)
else:
hanoil(level-1,fromWhere,toWhere,temp)
hanoil(1,fromWhere,temp,toWhere)
hanoil(level-1,temp,fromWhere,toWhere)
n += 1
level = input('输入汉诺塔层数:')
hanoil(int(level),1,2,3)
这三种算法的原理一模一样,是不是乍一看不一样,哈哈哈别被骗了,下面这三句话作用一样,如果以Python的为例使用函数调用的话可能好理解一点,不过都一样啦,三种方式实现的效果也一模一样,感兴趣的自己运行下试试。
hanoil(1,fromWhere,temp,toWhere)
System.out.println("Step"+(n++)+"从"+from+"移动到"+to);
printf("Step%d从%d移动到%d\n",n,from,to);
步骤如下:
Python
:总共两情况4步
- 如果是一层:
- Step:从起点木桩到目标木桩。
- 如果不是一层:
- Step1:将前n-1个从起点木桩移动到中转木桩。
- Step2:将剩下的一个直接移动到目标木桩。
- Step3:将中转木桩的n-1个从中转木桩移动到目标木桩。
Java
public class HANOIL {
public static void main(String[] args) throws IOException {
int level;
String str;
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入汉诺塔层数:");
str = bf.readLine();
level = Integer.parseInt(str);
Hanoil(level,1,2,3);
}
private static int n = 1;
public static void Hanoil (int level, int from, int temp, int to) {
if (level == 1)
System.out.println("Step"+(n++)+"从"+from+"移动到"+to);
else {
Hanoil(level-1,from,to,temp);
System.out.println("Step"+(n++)+"从"+from+"移动到"+to);
Hanoil(level-1,temp,from,to);
}
}
}
#include <stdio.h>
void Hanoi(int n,int from,int temp,int to);
int main(void)
{
int n;
printf("请输入汉诺塔层数:");
scanf("%d",&n);
Hanoi(n,1,2,3);
return 0;
}
void Hanoi(int n,int from,int temp,int to)
{
if (n == 1)
{
printf("Step%d从%d移动到%d\n",n,from,to);
}
else
{
Hanoi(n-1,from,to,temp);
printf("Step%d从%d移动到%d\n",n,from,to);
Hanoi(n-1,temp,from,to);
}
}