题目描述
汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有n层的时候,打印最优移动过程和最优移动总步数。
输入描述:
输入一个数n,表示塔层数
输出描述:
按样例格式输出最优移动过程和最优移动总步数
示例1
输入
2
输出
Move 1 from left to mid Move 1 from mid to right Move 2 from left to mid Move 1 from right to mid Move 1 from mid to left Move 2 from mid to right Move 1 from left to mid Move 1 from mid to right It will move 8 steps.
说明
当塔数为两层时,最上层的塔记为1,最下层的塔记为2
备注:
1 ≤ n ≤ 12
Solution
首先来看下递归解法:
import java.util.*;
public class Main {
private static String left = "left";
private static String mid = "mid";
private static String right = "right";
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
System.out.println("It will move " + hanoiProblem(n) + " steps.");
}
/**
* 递归解法
*
* @param num 汉诺塔层数
* @return 返回层数
*/
public static int hanoiProblem(int num) {
if (num < 1) {
return 0;
}
return process(num, left, right);
}
public static int process(int num, String from, String to) {
if (num == 1) {
// 从中间出发或者到达中间,只需要一步
if (from.equals(mid) || to.equals(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;
}
}
// 从中间出发或者到达中间
if (from.equals(mid) || to.equals(mid)) {
// another 为除去目标和来源的另一个
String another = (from.equals(left)) || to.equals(left) ? right : left;
// 1. 先把 1 ~ n-1 层复制到另一个
int count1 = process(num - 1, from, another);
// 2. 把第 n 层复制到目的地,因为目标和来源中有一个是 mid, 所以只需一步
int count2 = 1;
System.out.println("Move " + num + " from " + from + " to " + to);
// 3. 把 1 ~ n-1 层从另一个复制回 to
int count3 = process(num - 1, another, to);
return count1 + count2 + count3;
} else {
// 要么从左到右,要么从右到左,所以 another 一定是 mid
// 1.先把 1 ~ n-1 从 from 复制到 to
int count1 = process(num - 1, from, to);
// 2.把 n 从 from 复制到 mid
int count2 = 1;
System.out.println("Move " + num + " from " + from + " to " + mid);
// 3.把 1 ~ n-1 从 to 复制到 mid,完成全部n层从 from 到 mid 的复制
// 4.把 1 ~ n-1 从 mid 复制到 from
int count3 = process(num - 1, to, from);
// 5.把 n 从 mid 复制到 to
int count5 = 1;
System.out.println("Move " + num + " from " + mid + " to " + to);
// 6.把 1 ~ n-1 从 from 复制到 mid,再从 mid 复制到 to
int count6 = process(num - 1, from, to);
return count1 + count2 + count3 + count5 + count6;
}
}
}
接下来来看非递归解法,也就是用栈来解决。
/**
* 非递归解法:使用栈来模拟
*/
public enum Action {
No, LtoM, MtoL, MtoR, RtoM
}
private static Action record;
/**
* 非递归解法
*
* @param num 盘子数量
* @return
*/
public static int hanoiProblemByStack(int num) {
Stack<Integer> lStack = new Stack<>();
Stack<Integer> mStack = new Stack<>();
Stack<Integer> rStack = new Stack<>();
// 先压入最大值,可以避免一开始和空栈比较时的报错处理
lStack.push(Integer.MAX_VALUE);
mStack.push(Integer.MAX_VALUE);
rStack.push(Integer.MAX_VALUE);
for (int i = num; i > 0; i--) { // 一开始盘子都在 left 栈
lStack.push(i);
}
record = Action.No; // record 用于记录前一个步骤
int step = 0;
// right 栈的大小为 num + 1,则说明任务完成,可以结束了
while (rStack.size() != num + 1) { // 1 2 3 4
// 总共可能的方法有四种:从left->mid, mid->left, mid->right, right->mid
// 但要注意的是,为了最优的方法,当前方法和前一个方法不能互逆,比如当前方法是left->mid,
// 则前一个方法不能是mid->left,否则当前方法就是执行完,那么这两个方法就等于白跑了
step += fStackTotStack(Action.MtoL, Action.LtoM, lStack, mStack, left, mid);
step += fStackTotStack(Action.MtoR, Action.RtoM, rStack, mStack, right, mid);
step += fStackTotStack(Action.LtoM, Action.MtoL, mStack, lStack, mid, left);
step += fStackTotStack(Action.RtoM, Action.MtoR, mStack, rStack, mid, right);
}
return step;
}
/**
* 判断从栈 fStack 的栈顶移动到 tStack 是否可行,可行则移动
*
* @param preNoAct 当前动作不允许的前置动作
* @param nowAct 当前动作
* @param fStack 来源栈
* @param tStack 目标栈
* @param from 来源位置
* @param to 目标位置
* @return
*/
public static int fStackTotStack(Action preNoAct, Action nowAct,
Stack<Integer> fStack, Stack<Integer> tStack,
String from, String to) {
// 判断前一个方法是否当前方法的互逆方法,不是的话,方可执行;
// 判断来源的栈顶和目标的栈顶孰大孰小,必须满足小顶压入大顶
if (record != preNoAct && fStack.peek() < tStack.peek()) {
tStack.push(fStack.pop());
System.out.println("Move " + tStack.peek() + " from " + from + " to " + to);
record = nowAct;
return 1;
}
return 0;
}