一、基本递归实现
下面是普遍的汉诺塔问题的递归解法代码
1 | public class Hanoi{ |
输出如下:
1 | x -> z |
二、基本非递归实现
非递归实现的方式本质就是尝试使用栈来模拟递归
1.创建一个保存状态的类
1 | public class State { |
2.实现主程序
1 | public class Hanoi { |
3.输出结果
1 | 非递归方式: |
三、汉诺塔问题扩展
我们尝试更改一下题目要求,不只是需要输出交换步骤,我们还需要记录交换过程中的三个柱子的圆盘存在情况
1.创建柱子类
该类用于表示汉诺塔的每一个柱子,并且这个类将记录每个柱子上的圆盘情况1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50public class HanoiPillar {
public int n; // 记录传递hanoi的圆盘数量
public String name; // 柱子名称
public ArrayList<Integer> arr = new ArrayList<>(); //用于记录当前柱子上所存在的圆盘
// 初始化A柱
public HanoiPillar(int n, String name) {
this.n = n;
this.name = name;
for (int i = n; i > 0; i--) {
this.arr.add(i);
}
}
// 初始化B柱和C柱
public HanoiPillar(String name) {
this.name = name;
}
// 判断该柱子上方是否为顶部盘子
public boolean top() {
boolean result = false;
if (!arr.isEmpty() && arr.size() != 0 && arr.get(arr.size() - 1) == 1) {
result = true;
}
return result;
}
public void moveTo(HanoiPillar hanoiPillar) {
hanoiPillar.arr.add(this.getDiskSize());
this.arr.remove(this.arr.size() - 1);
System.out.println(this.name + " -> " + hanoiPillar.name);
}
// 得到当前柱子的圆盘的列表,转化为String
public String getStore() {
StringBuilder result = new StringBuilder();
if (this.arr.size() > 0) {
for (int i = this.arr.size() - 1; i >= 0; i--) {
result.append(this.arr.get(i)).append(",");
}
}
return result.length() == 0 ? "null" : result.toString().trim();
}
// 得到该柱子中最小的圆盘的数值。以1、2、3、4、......、n来表示各个圆盘的大小。并且方便比较
public Integer getDiskSize() {
return this.arr.get(this.arr.size() - 1);
}
}
2.实现主程序
1 | public class Hanoi { |
3.输出结果
1 | A柱:1,2,3, B柱:null C柱:null A -> C |
四、汉诺塔问题改编(递归实现)
今天在做《程序员代码面试指南:IT名企算法与数据结构题目最优解(第二版)》时,遇到了一个稍微复杂一些的汉诺塔问题,但是理解之后发现本体只是在上面简易递归的基础上进行优化。
1.题目要求
1 | 【题目】 |
2.情况分析
首先我们尝试使用递归方式实现,然后进行常见情况分析
- 假设剩余N层塔都在
左
,希望全都移到中
,则有下面三个步骤- 将1~N-1层从
左
移到右
,该过程为递归 - 将N层从
左
移到中
- 将1~N-1层从
右
移到中
,该过程为递归
- 将1~N-1层从
- 假设剩余N层塔都是从
中
移到右
,或者从中
移到左
,或者从有右
到中
,其实原理与情况1相同,所以不做赘述
- 假设剩余N层塔都在
左
,希望都移到右
,则有下面五个步骤- 将1~N-1层从
左
移到右
,该过程为递归 - 将N层从
左
移到中
- 将1~N-1层从
右
移到左
,此过程为递归 - 将N层从
中
移到右
- 将1~N-1层从
左
移到右
,此过程为递归
- 将1~N-1层从
3.实现主程序
1 | public class Hanoi { |
4.输出结果
1 | Move 1 from x to y |
五、汉诺塔问题改编(非递归实现)
1.题目分析
我们把左、中、右三个地点抽象成栈,依次记为LS、MS和RS。最初所有的塔都在LS上。那么如上4个动作就可以看作是:某一个栈(from)把栈顶元素弹出,然后压入到另一个栈里(to),作为这一个栈(to)的栈顶。
例如,如果是7层塔,在最初时所有的塔都在LS上,LS从栈顶到栈底就依次是1~7,如果现在发生了“左”到“中”的动作,这个动作对应的操作是LS栈将栈顶元素1弹出,然后1压入到MS栈中,成为MS的栈顶。其他操作同理。
一个动作能发生的先决条件是不违反小压大的原则。
from栈弹出的元素num如果想压入到to栈中,那么num的值必须小于当前to栈的栈顶。还有一个原则不是很明显,但也是非常重要的,叫相邻不可逆原则,解释如下:
- 我们把4个动作依次定义为:
L->M
、M->L
、M->R
和R->M
。 - 很明显,
L->M
和M->L
过程互为逆过程,M->R
和R->M
互为逆过程。 - 在修改后的汉诺塔游戏中,如果想走出最少步数,那么任何两个相邻的动作都不是互为逆过程的。举个例子:如果上一步的动作是
L->M
,那么这一步绝不可能是M->L
,直观地解释为:你在上一步把一个栈顶数从“左”移动到“中”,这一步为什么又要移回去呢?这必然不是取得最小步数的走法。同理,M->R
动作和R->M
动作也不可能相邻发生。
有了小压大和相邻不可逆原则后,可以推导出两个十分有用的结论–非递归的方法核心结论:
- 游戏的第一个动作一定是
L->M
,这是显而易见的。 - 在走出最少步数过程中的任何时刻,4个动作中只有一个动作不违反小压大和相邻不可逆原则,另外三个动作一定都会违反。
对于结论2,现在进行简单的证明。
因为游戏的第一个动作已经确定是L->M
,则以后的每一步都会有前一步的动作。
假设前一步的动作是L->M
:
- 根据小压大原则,
L->M
的动作不会重复发生。 - 根据相邻不可逆原则,
M->L
的动作也不该发生。 - 根据小压大原则,
M->R
和R->M
只会有一个达标。
假设前一步的动作是M->L
:
- 根据小压大原则,
M->L
的动作不会重复发生。 - 根据相邻不可逆原则,
L->M
的动作也不该发生。 - 根据小压大原则,
M->R
和R->M
只会有一个达标。
假设前一步的动作是M->R
:
- 根据小压大原则,
M->R
的动作不会重复发生。 - 根据相邻不可逆原则,
R->M
的动作也不该发生。 - 根据小压大原则,
L->M
和M->L
只会有一个达标。
假设前一步的动作是R->M
:
- 根据小压大原则,
R->M
的动作不会重复发生。 - 根据相邻不可逆原则,
M->R
的动作也不该发生。 - 根据小压大原则,
L->M
和M->L
只会有一个达标。
综上所述,每一步只会有一个动作达标。那么只要每走一步都根据这两个原则考查所有的动作就可以,哪个动作达标就走哪个动作,反正每次都只有一个动作满足要求,按顺序走下来即可
2.实现主程序
1 | public class Hanoi { |
3.输出结果
1 | Move 1 from 左 to 中 |
参考视频
https://www.bilibili.com/video/av31023017?from=search&seid=15595573244367663980
参考文章
https://blog.csdn.net/weixin_42636076/article/details/81031580
https://www.jb51.net/article/128701.htm