目录
一、知识准备
1.问题背景与具体描述
汉诺塔(Hanoi Tower)问题是一个经典的递归问题,相信大家或多或少的都有所耳闻,下面我们就来看看这个问题的历史背景与具体描述。
汉诺塔(Hanoi Tower),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在其中一根柱子上从下往上按由大到小的顺序叠放了64片黄金圆盘,大梵天命令婆罗门把圆盘从下面开始按由大到小的顺序重新摆放在另一根柱子上,规定任何时候小圆盘上面都不能叠放大圆盘,且在三根柱子之间一次只能移动一个圆盘。
初步了解了汉诺塔的背景后,我们现在来具体描述一下这个问题:给定A、B、C三根柱子,现在A柱上叠放了n个圆盘(位于下层的圆盘一定比上层圆盘大)
要求:如何移动才能将这些圆盘重新叠放到C柱上,且在C柱上仍然满足下层圆盘比上层圆盘大
规定:在移动的过程中大圆盘不能放在小圆盘上,且一次只能移动一个圆盘(不过移动时,圆盘可以放在A、B、C任意一根柱子上)
2.解题思路
为得出解题思路,我们先通过n=1、n=2、n=3这三个简单的例子进行探索。
当n=1时,此时显然只需要一步,直接将A柱上的圆盘1移动到C柱上即可。
当n=2时,需要以下三步:
- 第一步,将A柱上的圆盘1移动到B柱上
- 第二步,将A柱剩下的圆盘2移动到C柱上
- 第三步,将B柱上的圆盘1移动到C柱上
当n=3时,需要以下七步:
- 第一步,将A柱上的圆盘1移动到C柱上
- 第二步,将A柱上的圆盘2移动到B柱上
- 第三步,将C柱上的圆盘1移动到B柱上
- 第四步,将A柱剩下的圆盘3移动到C柱上
- 第五步,将B柱上的圆盘1移动到A柱上
- 第六步,将B柱上的圆盘2移动到C柱上
- 第七步,将A柱上的圆盘1移动到C柱上
观察上面三个例子(特别是n=3时),我们可以发现,要完成Hanoi塔问题,一共需要三大步骤:
- 第一步,将A柱上除最底层圆盘之外的所有圆盘,在借助C柱的情况下全部移动到B柱上
- 第二步,将A柱上剩下的最后一个圆盘移动到C柱上
- 第三步,在借助A柱的情况下,将B柱上的所有圆盘全部移动到C柱上
由此类推到n个圆盘时,也是这三大步骤:
- 第一步,将A柱上的n-1个圆盘,借助C柱移动到B柱上
- 第二步,将A柱上剩下的圆盘n移动到C柱上
- 第三步,将B柱上的n-1个圆盘,借助A柱移动到C柱上
如果我们将移动前摆放圆盘的柱子称为起始柱,圆盘需要移动到的柱子称为目标柱,中间借助的柱子称为中间柱,那么显然最开始的时候起始柱为A,中间柱为B,目标柱为C。
- 当n=1时,直接将当前起始柱A上的圆盘移动到当前目标柱C上。
- 当n≥2时,首先将最开始的中间柱与目标柱交换,得到此时起始柱为A,中间柱为C,目标柱为B,再将此时起始柱A上的n-1个圆盘移动到此时的目标柱B上(即我们刚刚总结的三大步骤中的第一步);其次,将A柱上剩下的唯一一个圆盘移动到C柱上(即我们刚刚总结的三大步骤中的第二步);再次,将最开始的起始柱与中间柱交换,得到此时起始柱为B,中间柱为A,目标柱为C,再将此时起始柱B上的n-1个圆盘移动到此时的目标柱C上(即我们刚刚总结的三大步骤中的第三步)。
需要注意,不管n为多少,“其次”这一步都是不会变的,始终都是将A柱上剩下的唯一一个圆盘移动到C柱上。
二、代码实现
知道基本的解题思路后,我们就可以开始考虑代码实现了,具体如下:
package datastructure.tree;
/**
*Hanoi tower.
*
*@auther Xin Lin 3101540094@qq.com.
*/
public class Hanoi {
/**
*********************
* Move a number of plates.
*
* @param paraSource The source pole.
* @param paraIntermedium The intermediary pole.
* @param paraDestination The destination pole.
* @param paraNumber The number of plates.
*********************
*/
public static void hanoi(char paraSource, char paraIntermediary, char paraDestination,
int paraNumber) {
if (paraNumber == 1) {
System.out.println(paraSource + "->" + paraDestination + " ");
return;
} // Of if
hanoi(paraSource, paraDestination, paraIntermediary, paraNumber - 1);
System.out.println(paraSource + "->" + paraDestination + " ");
hanoi(paraIntermediary, paraSource, paraDestination, paraNumber - 1);
}// Of hanoi
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String args[]) {
hanoi('a', 'b', 'c', 3);
} // Of main
} // Of class Hanoi
根据我们之前的分析可知,一共需要用到四个参数,包括起始柱paraSource、中间柱paraIntermediary、目标柱paraDestination以及圆盘数目paraNumber。同时,这里我们使用“->”来表示圆盘的移动方向,比如 a -> c 就表示圆盘从柱a移动到柱c。
我们在上面提到,当n≥2时会执行一轮“首先”——“其次”——“再次”,显然在这个过程中得到的n-1仍然有可能≥2,所以我们考虑利用递归思想,当得到的n-1仍然≥2时,则再执行一轮“首先”——“其次”——“再次”,直到n=1时结束递归。因此,就可以得到上述递归函数。
运行结果如下:
可以看到,运行结果与我们之前分析n=3时的结果是一致的。
总结
今天我们主要是利用分治的思想,采取递归的方式来解决汉诺塔(Hanoi Tower)问题,其中递归的结束条件是比较容易找到的——当圆盘数目=1时直接将A柱上的圆盘移动到C柱上,不过剩下的递归操作还是需要仔细观察分析一下。由于递归结束条件为圆盘数目=1,所以我们是从圆盘数目=n时开始逐步递归,直到圆盘数目=1时结束递归,在递归的过程中最容易出错的就是起始柱、中间柱、目标柱这三者的变换,简言之(仅针对于本文创建的递归函数)只要是输入的第一个参数,就是起始柱(不管输入的是A柱、B柱还是C柱),只要是输入的第二个参数,就是中间柱(不管输入的是A柱、B柱还是C柱),只要是输入的第三个参数,就是目标柱(不管输入的是A柱子、B柱还是C柱)。