描述
汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有n层的时候,打印最优移动过程和最优移动总步数。
输入描述:
输入一个数n,表示塔层数
输出描述:
按样例格式输出最优移动过程和最优移动总步数
示例
输入: 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:递归的方法;
方法2:非递归的方法,用栈来模拟汉诺塔的三个塔;
方法1:递归的方法
先来看看一般的汉诺塔问题,A,B,C三个柱子,将A柱子上的圆盘移动到C上面,但是要保持小盘必须落在大盘上面才可以直接移动,并且一次只能移动一个圆盘。通过递归求解,如果n-1个盘子已经移动到B,那么n直接移动到C,然后将n-1从B移动到C即可。
1)、将n-1个圆盘从A移动到B,借助C hanota(n-1, A, C, B)
2)、将n从A移动到C move(n, A, C)
3)、将n-1圆盘从B移动都C,借助A hanota(n-1, B, A, C)
代码如下:
import java.util.Scanner; public class Hanota { public static void move(int n, String from, String to){ System.out.println("将"+ n + "从" + from + "移动到" + to); } public static void hanota(int n, String A, String B, String C){ if(n == 1){ move(n, A, C); }else{ hanota(n-1, A, C, B); move(n, A, C); hanota(n-1, B, A, C); } } public static void main(String [] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); hanota(n, "A", "B", "C"); } }
现在多了限制,就是移动必须经过中间的B,不能直接从A到C或者从C到A,那步骤修改一下:
1)、将n-1个圆盘从A移动到C,借助B hanota(n-1, A, B, C)
2)、将n从A移动到B move(n-1, A, B)
3)、将n-1个圆盘从C移动到A,借助B hanota(n-1, C,B,A)
4)、将n从B移动到C move(n, B, C)
5)、将n-1个圆盘从A移动到C,借助B hanota(n-1, A, B, C)
代码如下:
import java.util.Scanner; public class Main{ static private int count = 0; public static void move(int n,String from,String to){ System.out.println("Move " + n + " from " + from + " to " + to); count++; } public static void hanota(int n,String A,String B,String C){ if(n == 1){ move(n,A,B); move(n,B,C); return; }else{ hanota(n-1,A,B,C); move(n,A,B); hanota(n-1,C,B,A); move(n,B,C); hanota(n-1,A,B,C); } } public static void main(String[] args){ Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); hanota(n,"left", "mid", "right"); System.out.println("It will move " + count + " steps."); } }
方法2:非递归的方法,用栈来模拟汉诺塔的三个塔
修改后的汉诺塔问题不能让任何塔从左直接移动到右,也不能从右直接移动到左,而是要经过中间,也就是说,实际上能做的动作,只有四个:左->中,中->左,中->右,右->中
用栈来模拟汉诺塔的移动,其实就是某一个栈弹出栈顶元素,压入到另一个栈中,作为另一个栈的栈顶,理解了这个就好说了,对于这个问题,有两个原则:
一:小压大原则,也就是,要压入的元素值不能大于要压入的栈的栈顶的元素值,这也是汉诺塔的基本规则;
二:相邻不可逆原则,也就是,我上一步的操作如果是左->中,那么下一步的操作一定不是中->左,否则就相当于是移过去又移回来;
有了这两个原则,就可以推导出两个非常有用的结论:
1、游戏的第一个动作一定是L->M;
2、在走出最小步数过程中的任何时刻,四个动作中只有一个动作不违反小压大和相邻不可逆原则,另外三个动作一定都会违反;
import java.util.Scanner; import java.util.Stack; public class Main{ static enum Action{ No,LToM,MToL,MToR,RToM } public static int fStackToStack(Action[] record,Action preNoAct,Action nowAct,Stack<Integer> fStack, Stack<Integer> tStack, String from, String to, StringBuilder sb){ if(record[0] != preNoAct && fStack.peek() < tStack.peek()){ tStack.push(fStack.pop()); sb.append("Move " + tStack.peek() + " from " + from + " to " + to).append("\n"); record[0] = nowAct; return 1; } return 0; } public static int hanoiProblem(int num,String left,String mid,String right,StringBuilder sb){ Stack<Integer> lS = new Stack<Integer>(); Stack<Integer> mS = new Stack<Integer>(); Stack<Integer> rS = new Stack<Integer>(); 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 += fStackToStack(record, Action.MToL, Action.LToM, lS, mS, left, mid, sb); step += fStackToStack(record, Action.LToM, Action.MToL, mS, lS, mid, left, sb); step += fStackToStack(record, Action.RToM, Action.MToR, mS, rS, mid, right, sb); step += fStackToStack(record, Action.MToR, Action.RToM, rS, mS, right, mid, sb); } sb.append("It will move " + step + " steps."); System.out.println(sb); return step; } public static void main(String[] args){ Scanner sc = new Scanner(System.in); int n = sc.nextInt(); String left = "left"; String mid = "mid"; String right = "right"; StringBuilder sb = new StringBuilder(); hanoiProblem(n,left,mid,right,sb); } }