算法-汉诺塔问题及递归 Java
1. 问题描述
相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。
2. 算法逻辑
前提:默认是最底下、最大的圆盘是第n个圆盘,设n远大于1。
(注:下面所说的都是想法,而不是具体实现,不要在看到这些文字时就代入代码里面,而是先理解这些文字的意思!)
要想完成整个移动,我们可以这样子想:
- 首先将第n个圆盘和上面的n-1个圆盘分别看做一个整体,首先我们要做的就是把上面的“n-1个圆盘”(这里说的是一个整体)移动到辅助柱上。
- 其次把第n个圆盘移动到目标柱上,到这一步就完成了最大的圆盘移动到了目标柱的最下面。
- 最后要把辅助柱上的n-1个圆盘(这里同样看做一个整体)移动到目标柱,这样就完成了所有移动。(这里要注意的是,因为从辅助柱移动到目标柱,本身又可以看作是一个汉诺塔问题,所以原本的辅助柱在这里变成了源柱,原本的源柱在这里变成了辅助柱。)
可能有人就会有疑问了,“每次只能移动一个盘子,这里一下移动了n-1个盘子,明显不符合要求啊“,暂且保留这个问题,顺着思路继续向下走。
3. 算法实现
3.1 方法声明
定义方法move
,用来实现递归算法,其中参数n表示圆盘的个数,a表示源柱,b表示辅助柱,c表示目录柱,算法最终实现“打印将源柱a上的圆盘移动到目标柱c上的所有步骤”。
/*
* @param n 圆盘数量
* @param a 源柱
* @param b 辅助柱
* @param c 目标柱
*/
public static void move(int n, char a, char b, char c) {
}
3.2 递归终止条件
当源柱上只有一个圆盘时,可以直接将源柱上的这个圆柱移动到目标柱上,这就是递归终止条件。
/*
* @param n 圆盘数量
* @param a 源柱,只是记录一个符号,没有实际含义
* @param b 辅助柱,只是记录一个符号,没有实际含义
* @param c 目标柱,只是记录一个符号,没有实际含义
*/
public static void move(int n, char a, char b, char c) {
if (n == 1) {
// 如果源柱上只有一个圆盘,直接将圆盘从源柱a上移动到目标柱c上
System.out.printf("从%s上移动第%d个圆盘到%s上\n", a, n, c);
}
}
3.3 递归非终止条件
在递归的非终止条件中,将完成算法逻辑中所示的三个步骤
public static void move(int n, char a, char b, char c) {
if (n == 1) {
// 如果源柱上只有一个圆盘,直接将圆盘从源柱a上移动到目标柱c上
System.out.printf("从%s上移动第%d个圆盘到%s上\n", a, n, c);
} else {
// 步骤1: 将 n - 1(从上至下)个圆盘,移动到辅助柱上
move(n - 1, a, c, b);
// 步骤2: 将第 n 个圆盘移动到目标柱上
System.out.printf("从%s上移动第%d个圆盘到%s上\n", a, n, c);
// 步骤3: 将 n - 1 个圆盘从辅助柱上移动到目标柱上,此时a作为辅助柱
move(n - 1, b, a, c);
}
}
4. 算法解析
4.1 步骤一解析
// 步骤1: 将 n - 1(从上至下)个圆盘,移动到辅助柱上
move(n - 1, a, c, b);
- 在移动n-1个圆盘时,由于使用的是递归算法,又因为n != 1,所以会再次调用该函数,此时 n = n - 1,我们且称之为 n1。
- 同样的在移动n1个圆盘时,n1 != 1,所以又调用该函数,此时 n = n1 -1,也即 n = n - 2,我们且称之为 n2。
- 为了更好的理解,这里再多分析一个步骤!在移动n2个圆盘时,n2!=1,所以又调用该函数,此时n=n2-1,即 n=n-3,我们且称之为n3。
- 直到递归调用n-1次后,此时n=n-(n-1)=1,满足递归终止条件,也就是在执行n-1次调用后,终于到了最上面的那个圆盘了,就可以开始移动了,直接将最上面的圆盘移动到“目标柱”上。注意注意这一步传入的“目标柱”是实际上调用方法时传入的辅助辅。
4.2 步骤二解析
// 步骤2: 将第 n 个圆盘移动到目标柱上
System.out.printf("从%s上移动第%d个圆盘到%s上\n", a, n, c);
- 步骤一最顶层函数执行完成后,最上面n-(n-1)那个元素已经完成了移动。
- 最顶层函数步骤二执行,将n-(n-2)移动到另一根柱子上。
4.3 步骤3解析
// 步骤3: 将 n - 1 个圆盘从辅助柱上移动到目标柱上,此时a作为辅助柱
move(n - 1, b, a, c);
- 执行到这里时,n-(n-2)已经在最下面了,些时要做的就是把n-(n-1)再移动回去。
至此,一次函数回归分析完毕,后续执行类似。
5. 汉诺塔算法
public class Hanoi {
public static void main(String[] args) {
move(3, 'a', 'b', 'c');
}
/*
* @param n 圆盘数量
* @param a 源柱
* @param b 辅助柱
* @param c 目标柱
*/
public static void move(int n, char a, char b, char c) {
if (n == 1) {
// 如果源柱上只有一个圆盘,直接从源柱a上移动到目标柱c上
System.out.printf("从%s上移动第%d个圆盘到%s上\n", a, n, c);
} else {
// 步骤1: 将 n - 1(从上至下)个圆盘,移动到辅助柱上
move(n - 1, a, c, b);
// 步骤2: 将第 n 个圆盘移动到目标柱上
System.out.printf("从%s上移动第%d个圆盘到%s上\n", a, n, c);
// 步骤3: 将 n - 1 个圆盘从辅助柱上移动到目标柱上,此时a作为辅助柱
move(n - 1, b, a, c);
}
}
}