第一章 栈和队列
1.6 用栈来求解汉诺塔问题
【题目】
汉诺塔问题比较经典,这里修改一下游戏规则:限制不能从最左侧的塔直接移动到最右侧,也不从最右侧直接移动到最左侧,必须经过中间。求当塔有 N 层的时候,打印最优移动过程和最优移动总步数。
【要求】
使用一下两种方法解决:
- 方法一:递归的方法;
- 方法二:非递归的方法,用栈模拟整个过程。
【难度】
校 ★★★☆
【题解】
方法一:【递归】
- 首先,如果只剩最上层的塔需要移动,则可能有以下过程:
- 从 “左” 移动到 “中”;
- 从 “中” 移动到 “左”;
- 从 “中” 移动到 “右”;
- 从 “右” 移动到 “中”;
- 从 “左” 移动到 “右”;
- 从 “右” 移动到 “左”;
- 以上是递归的终止条件,也就是只剩下最上层塔的移动过程。
- 接下来分析剩下多层塔的情况。
- 如果剩下 N 层塔,从上到下一次是 1 ~ N,则有如下过程:
- 如果剩下的 N 层塔都在 “左”,全部移到 “中”,有三个步骤:
- 将 1 ~ N-1 层塔先全部从 “左” 移动到 “右”,交给递归完成;
- 将第 N 层塔从 “左” 移动到 “中”;
- 再将 1 ~ N-1 层塔全部从 “右” 移动到 “中”,交给递归完成。
- 如果把剩下的 N 层塔从 “中” 移动到 “左”,从 “中” 移动到 “右”,从 “右” 移动到 “中”,过程与 a 类似似,同样分为三步完成,这里不再赘述。
- 如果剩下的 N 层塔都在 “左”,全部移动到 “右”,右五个步骤:
- 将 1 ~ N-1 层塔先全部从 “左” 移动到 “右”,交给递归完成;
- 将第 N 层塔从 “左” 移动到 “中”;
- 将 1 ~ N-1 层塔全部从 “右” 移动到 “左”,交给递归完成;
- 将第 N 层塔从 “中” 移动到 “右”;
- 最后将 1 ~ N-1 层塔全部从 “左” 移动到 “右”,交给递归完成;
- 如果剩下的 N 层塔从 “右” 移动到 “左”,过程与 c 类似,同样分为五步完成,不在赘述。
方法二:【非递归】
- 修改后的汉诺塔问题不能让任何塔直接从 “左” 移动到 “右”,也不能直接从 “右” 移动到 “左”,必须经过中间。这样实际的动作只有四个:
- 从 “左” 到 “中”
- 从 “中” 到 “左”
- 从 “中” 到 “右”
- 从 “右” 到 “中”
- 把左、中、右三个位置抽象为栈,记为 LS、MS 和 RS。最开始所有的塔都在 LS 上。那么以上四个动作可以看作为:某一个栈 (from)弹出栈顶元素,然后压入另一个栈(to)中。
- 这种实现需要不能违反两个原则:
- 一个动作能发生的先决条件是不违反小压大的原则:即栈 from 栈顶弹出的元素 num 如果要压入栈 to 中,那么 num 的值必须小于栈 to 栈顶元素。
- 还不能违反相邻不可逆原则:
- 把四个动作定义为 L2M、M2L、M2R 和 R2M;
- L2M 和 M2L 互为逆过程,M2R 和 R2M 同样互为逆过程;
- 在汉诺塔游戏中,如果得到最少步数,那么任何两个相邻的动作都不是互为逆过程的。
- 为了满足小压大和相邻不可逆原则,必有如下结论:
- 游戏的第一个动作一定是 L2M;
- 任一过程中,四个动作中只会有一个动作不违反原则,另外三个一定会违反。
- 综上所述,每一步只会有一个动作满足条件,那么每走一步可以根据这两个原则来判断下一步发生的动作。
【实现】
- Hanoi.java
import java.util.Objects;
import java.util.Stack;
public class Hanoi {
private Hanoi() {}
public static int recursionImpl(int num, final String left, final String mid, final String right) {
if (num < 1) {
return 0;
}
return recursion(num, left, mid, right, left, right);
}
private static int recursion(int num, final String left, final String mid, final String right, String from, String to) {
if (Objects.equals(from, to)) {
return 0;
}
if (num == 1) {
if (Objects.equals(from, mid) || Objects.equals(to, mid)) {
System.out.println("Move 1 from " + from + " to " + to);
return 1;
} else {
System.out.println("Move 1 from " + from + " to " + mid);
System.out.println("Move 1 from " + mid + " to " + to);
return 2;
}
} else {
if (Objects.equals(from, mid) || Objects.equals(to, mid)) {
String another = (Objects.equals(from, left) || Objects.equals(to, left)) ? right : left;
int step1 = recursion(num - 1, left, mid, right, from, another);
int step2 = 1;
System.out.println("Move 1 from " + from + " to " + to);
int step3 = recursion(num - 1, left, mid, right, another, to);
return step1 + step2 + step3;
} else {
int step1 = recursion(num - 1, left, mid, right, from, to);
int step2 = 1;
System.out.println("Move 1 from " + from + " to " + mid);
int step3 = recursion(num - 1, left, mid, right, to, from);
int step4 = 1;
System.out.println("Move 1 from " + mid + " to " + to);
int step5 = recursion(num - 1, left, mid, right, from, to);
return step1 + step2 + step3 + step4 + step5;
}
}
}
private static enum Action {
NO, L2M, M2L, M2R, R2M
}
public static int stackImpl(int num, final String left, final String mid, final String right) {
final Stack<Integer> LS = new Stack<>();
final Stack<Integer> MS = new Stack<>();
final Stack<Integer> RS = new Stack<>();
LS.push(Integer.MAX_VALUE);
MS.push(Integer.MAX_VALUE);
RS.push(Integer.MAX_VALUE);
for (int i = num; i > 0; --i) {
LS.push(i);
}
Action[] record = { Action.NO };
int step = 0;
while (RS.size() != num + 1) {
step += move(record, Action.M2L, Action.L2M, LS, MS, left, mid);
step += move(record, Action.L2M, Action.M2L, MS, LS, mid, left);
step += move(record, Action.R2M, Action.M2R, MS, RS, mid, right);
step += move(record, Action.M2R, Action.R2M, RS, MS, right, mid);
}
return step;
}
private static int move(Action[] pre, Action no, Action now, final Stack<Integer> FS, final Stack<Integer> TS, String from, String to) {
if (!Objects.equals(pre[0], no) && FS.peek() < TS.peek()) {
TS.push(FS.pop());
System.out.println("Move 1 from " + from + " to " + to);
pre[0] = now;
return 1;
}
return 0;
}
}
public class HanoiTest {
public static void main(String[] args) {
final String left = "left";
final String mid = "mid";
final String right = "right";
int num = 5;
Hanoi.recursionImpl(num, left, mid, right);
System.out.println("————————————————————————————————————————");
Hanoi.stackImpl(num, left, mid, right);
}
}