汉诺塔问题
相传在古印度的一座圣庙中 ,有一种叫做汉诺塔的游戏。该游戏是在一块铜板装置上,有三根杆子A、B、C,初始,A杆上从上到下由小到大放着盘子。游戏目的与规则,把A杆上的金盘全部移动到C杆上,并仍保持着原有的顺序。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持着大盘子在下,小盘在上,移动过程中盘子可以在A、B、C任意一个杆上。
解决思路
解决汉诺塔相关问题的关键思想就是腾地方的思想。
以三层汉诺塔为例,从上到下分别给盘子取名为盘1,盘2,盘3,从左到右分别取左柱,中柱,右柱。
由于汉诺塔的规则问题,我们不能做到直接让盘1、盘2、盘3整体直接从左柱移动到右柱上,所以得拆分步骤。
让三个盘子从左柱都移动到右柱整体步骤可以拆分为:
一、 盘1,盘2从左柱移动到中柱(leftToMid)。
二、盘3从左柱移动到右柱(leftToRight)。
三、 盘1,盘2从中柱移动到右柱(midToRight)。
但是由于汉诺塔问题有着移动过程中三根杆上都始终保持着大盘子在下,小盘在上的规则,所有上面整体步骤1与步骤3还是不能一步实现的,所以我们对步骤继续进行拆分。
步骤一盘1,盘2从左柱移动到右柱整体步骤可以拆分为:
-
盘1从左柱移动到右柱(leftToRight)。
-
盘2从左柱移动到中柱(leftToMid)。
-
盘1从右柱移动到左柱(rightToLeft)。
步骤一盘1,盘2从中柱移动到右柱整体步骤可以拆分为:
-
盘1从中柱移动到左柱(midToLeft)。
-
盘2从中柱移动到右柱(midToRight)。
-
盘1从左柱移动到右柱(leftToRight)。
可以看出,3个盘子从左柱移动到右柱的过程中,要想要盘3从左柱移动到右柱上,必须让盘1、盘2 腾地方,从左柱移动到中柱上,同理,要想要盘1、盘2从中柱移动到右柱上,又必须让盘1 腾地方,让盘1从中柱移动到左柱上。
得出,要想要N个盘子实现从左移动到右柱,可以分解为:N-1盘子 腾地方从左柱移动到中柱上,第N个盘子从左柱移动到右柱上,N-1盘子从中柱上到右柱上。同理,步骤N-1盘子 腾地方从左柱移动到中柱上又可以分解和步骤N-1盘子从中柱上到右柱上又可以进行分解成上面相似的步骤。
所以,我们可以通过大步骤不断分解为小步骤从而得出了汉诺塔问题的结果,汉诺塔问题的 腾地方 的思想就完全体现出来,因为只有三根柱子,所以不管怎么分解,移动的方式只有6种(leftToRight、midToRight、leftToMid、midToLeft、rightToMid、rightToLeft)那么我们打印n层汉诺塔从最左边移动到最右边的全部过程第一种代码表示就可以直白的用这6种方式实现。
解法一
public static void Hanoi1(int n) {
leftToRight(n);
}
private static void leftToRight(int n) {
if (n==1) {
System.out.println("Move 1 from left to right.");
return;
}
leftToMid(n-1);
System.out.println("Move "+n+" from left to right.");
midToRight(n-1);
}
private static void midToRight(int n) {
if (n==1) {
System.out.println("Move 1 from mid to right.");
return;
}
midToLeft(n-1);
System.out.println("Move "+n+" from mid to right.");
leftToRight(n-1);
}
private static void leftToMid(int n) {
if (n==1) {
System.out.println("Move 1 from left to mid.");
return;
}
leftToRight(n-1);
System.out.println("Move "+n+" from left to mid.");
rightToMid(n-1);
}
private static void midToLeft(int n) {
if (n==1) {
System.out.println("Move 1 from mid to left.");
return;
}
midToRight(n-1);
System.out.println("Move "+n+" from mid to left.");
rightToMid(n-1);
}
private static void rightToMid(int n) {
if (n==1) {
System.out.println("Move 1 from right to mid.");
return;
}
rightToLeft(n-1);
System.out.println("Move "+n+" from right to mid.");
leftToMid(n-1);
}
private static void rightToLeft(int n) {
if (n==1) {
System.out.println("Move 1 from right to left.");
return;
}
rightToMid(n-1);
System.out.println("Move "+n+" from right to left.");
midToLeft(n-1);
}
可以看出方法1虽然很直白,但是代码却很繁琐,不妨把代码更加抽象化,每次 腾地方 的过程都可以抽象为从本柱到另外一个柱上,这样就能得出我们的第二种递归打印n层汉诺塔从最左边移动到最右边的全部过程的代码。
解法二
public static void Hanoi2(int n) {
if (n>0) {
fun(n,"left","right","mid");
}
}
public static void fun(int n,String from,String to,String other) {
if (n==1) {
System.out.println("Move 1 from "+from+" to "+to+"." );
}else{
fun(n-1,from,other,to);
System.out.println("Move "+n+" from "+from+" to "+to+".");
fun(n-1,other,to,from);
}
}