据传开天辟地之神勃拉玛在一个庙里留下了三根金刚石的棒,第一根上面套着64个圆的金片,最大的一个在底下,其余一个比一个小,依次叠上去,庙里的众僧不倦地把它们一个个地从这根棒搬到另一根棒上,规定可利用中间的一根棒作为帮助,但每次只能搬一个,而且大的不能放在小的上面。就是这看似简单的问题,却困扰了人们千年以上。
后来,这个传说就演变为汉诺塔游戏,玩法如下:
1.有三根杆子A,B,C。A杆上有若干碟子
2.每次移动一块碟子,小的只能叠在大的上面
3.把所有碟子从A杆全部移到C杆上
技术要点
为了将N个盘子从A移动到C,需要先将第N个盘子上面的N-1个盘子移动到B上,这样才能将第N个盘子移动到C上。同理,为了将第N-1个盘子从B移动到C上,需要将N-2个盘子移动到A上,这样才能将第N-1个盘子移动到C上。通过递归就可以实现汉诺塔问题的求解。
理解一:
通过二叉树的中序遍历过程来分析汉诺塔问题:
考虑A、B、C三根柱子,A上从上到下有1、2、3三个数,要把A上的数移动到C上,其过程应该是
A B C
初始 123
A->C 23 1
A->B 3 2 1
C->B 3 12
A->C 12 3
B->A 1 2 3
B->C 1 23
A->C 123
可以写成下图二叉树的中序遍历
在程序中有两个输出语句,
结束条件中的数据语句输出的是二叉树中的叶子结点
递归左子树和递归右子树中间的输出语句输出是所有的非叶子结点
所有左孩子和右孩子输出语句中得到的实际参数都是其父结点传递的,根节点输出语句得到的参数是初始传递的参数
<1>首先确定递归函数的形式
1
|
doTower(
int
topN,
char
from,
char
inter,
char
to)
|
<2>在main中给根节点传递的初始值为(A,B,C)
对于根节点的输出A->C
可以确定递归左子树和递归右子树中间的输出语句应该输出 from->to,即
1
|
System.out.println(
"Disk "
+topN+
" from "
+from+
" to "
+to);
|
<3>步骤<2>确定了递归左子树和递归右子树中间的输出语句的格式,
对于根节点(A->C)的左孩子(A->B)和右孩子(B->C),由于都是非叶子结点,所以它们使用的仍然是递归左子树和递归右子树中间的输出语句 from->to,
所以对于左孩子(A->B),要输出(A->B),可以确定出给其传递的参数应该是(A,C,B)
对于右孩子(B->C),要输出(B->C),可以确定出给其传递的参数应该是(B,A,C)
又因为所有左孩子和右孩子输出语句中得到的实际参数都是其父结点传递的,由于父节点得到的实参是(frmo=A,inter=B,to=C)
左孩子要想得到(A,C,B),给其传递的顺序应该是(from,to,inter)
右孩子要想得到(B,A,C),给其传递的顺序应该是(inter,from,to)
即确定了
1
2
3
|
doTower(topN-
1
, from, to, inter);
doTower(topN-
1
, inter, from, to);
|
<4>最后确定结束条件中的输出语句的输出顺序
由于上一步确定了
1
2
3
|
doTower(topN-
1
, from, to, inter);
doTower(topN-
1
, inter, from, to);
|
对于A->B的左孩子A->C,由于在A->B结点中,from=A,inter=C,to=B,所以执行了doTower(topN-1, from, to, inter)后,给A->C传递的参数应该是(A,B,C),想要输出A->C,可以确定结束条件中的输出语句为
1
|
System.out.println(
"Disk 1 from "
+from+
" to "
+to);
|
同理,对于A->B的右孩子C->B,由于在A->B结点中,from=A,inter=C,to=B,所以执行了doTower(topN-1, inter, from, to)后,给C->B传递的参数应该是(C,A,B),可以输出C->B
理解二:
我觉得这样理解最高分的答案会比较简单(粗暴)~
一个环:
Step1.将最大的环从A移动到C
A -> C
两个环:
Step1.把除了最大的环之外的环,从A移动到B
A -> B
Step2.将最大的环从A移动到C
A -> C
Step3.把除了最大的环之外的环,从B移动到C
B -> C
三个环:
Step1.把除了最大的环之外的环,从A移动到B
A -> C
A -> B
C -> B
Step2.将最大的环从A移动到C
A -> C
Step3.把除了最大的环之外的环,从B移动到C
B -> A
B -> C
A -> C
这个时候,可以放张图了
(b)是step1完成的时候的状态,已经将所有的n-1,这里也就是3个环从A挪到了B
<第一处递归,move(n-1,A,C,B) 这个函数要实现将n-1个环从A,借助C,移动到B>
(c)是step2,此时需要将第n个,也就是第四个最大的环从A挪到C
<move(1,A,B,C),或者干脆直接print("A -> C")>
(d)是step3,此时需要将B上面的n-1个环从B挪到C<第二处递归>
<第二处递归,move(n-1,B,A,C) 这个函数要实现将n-1个环从B,借助A,移动到C>
- 递归分析(有兴趣的可以去研究非递归的):
- 移动n个圆盘从a到c,必须先将n-1个金片从a经过c移动到b,移动n-1个问题相同,但规模变小.
- * 1.将n-1个圆盘从a经过c移动到b
- * 2.将第n个圆盘移动到c
- * 3.再将n-1个圆盘从b经过a移动到c
package GetOffer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class HanoiTower {
public static void moveDish(int level,char from,char inner,char to){
if(level==1){//当n只有1个的时候直接从a移动到c
System.out.println("从" + from + " 移动盘子" + level + " 号到" + to);
}else{
moveDish(level - 1 ,from,to,inner);
System.out.println("从" + from + " 移动盘子" + level + " 号到" + to);
moveDish(level - 1 ,inner,from,to);//第n-1个移动过来之后b变开始盘,b通过a移动到c
}
}
public static void main(String[] args) throws Exception, IOException {
// int nDishs = 2;
// moveDish(nDishs,'A','B','C');
//输入方法一
// int nDishs;
// Scanner scan = new Scanner(System.in);
// System.out.println("请输入盘子数:");
// nDishs = scan.nextInt();
// moveDish(nDishs,'A','B','C');
//输入方法二
int nDishs;
BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入盘子数:");
nDishs = Integer.parseInt(buf.readLine());
moveDish(nDishs,'A','B','C');
}
}