一、基本递归实现
下面是普遍的汉诺塔问题的递归解法代码
| 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