修改汉诺塔问题的游戏规则:限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须通过中间。求当塔有N层的时候,打印最优移动和最优移动总步数。java
要求:ide
- 方法一:递归的方法
- 方法二:非递归的方法,用栈来模拟汉诺塔的三个塔
首先,若是只剩最上层的塔须要移动,则有以下处理:code
以上就是递归的终止条件,也就是只剩上层塔时的打印过程。递归
多层塔的时候。游戏
若是剩下N层塔,从最上到最小依次为1~N-1,则有以下判断部署
若是剩下的N层塔都在左,但愿所有移到中,则有三个步骤it
1)将1~N-1层塔先所有从左移动到右,交给递归过程io
2)将第N层塔从左移到中class
3)将1~N-1层塔所有从右移到中,明显交给递归过程方法
若是把剩下的N层塔从中移到左,从中移到右,从右移到中过程与上述相同
若是剩下的N层都在左,但愿所有移动到右。
1)将1~N-1层塔先所有从左移动到右,交给递归
2)将第N层塔从左移动到中
3)将1~N-1层塔从右移到左
4)N层从中移到右
5)最后将1~N-1层塔所有从左移到右,交给递归过程
若是剩下全在右,但愿移到左,同上
public static int hanoiProblem1(int num, String left, String mid, String right) { if (num < 1) { return 0; } return process(num, left, mid, right, left, right); } public static int process(int num, String left, String mid, String right, String from, String to) { //只有一层须要移动的时候 if (num == 1) { //若是from或to有一个是mid,说明是从右往中,或从左往中,直接移动便可。 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; } } //当层数大于1的时候,且有一个是到中间 if (from.equals(mid) || to.equals(mid)) { //若是是从左到中,或者中到左 another就为右,不然就为左,就是说如今参与的是左中,那么剩下的一个是右,同理右中 String another = (from.equals(left) || to.equals(left)) ? right : left; //递归处理n-1层,从当前的from到另外一端,即从作到右,或从右到左 int part1 = process(num - 1, left, mid, right, from, another); //将第N层塔从左移到中或从右移到中 int part2 = 1; System.out.println("Move " + num + " from " + from + " to " + to); //将1~N-1层塔所有从右移到中,或者从左移动中 int part3 = process(num - 1, left, mid, right, another, to); return part1 + part2 + part3; } else { //从左到右或者从右到左 递归处理n-1层 //将1~N-1层塔先所有从左移动到右或从右移到左 int part1 = process(num - 1, left, mid, right, from, to); //将第N层塔从左移动到中 或从右移到中 int part2 = 1; System.out.println("Move " + num + " from " + from + " to " + mid); //将1~N-1层塔从右移到左 或相反 int part3 = process(num - 1, left, mid, right, to, from); //N层从中移到右 int part4 = 1; System.out.println("Move " + num + " from " + mid + " to " + to); //最后将1~N-1层塔所有从左移到右,交给递归过程 int part5 = process(num - 1, left, mid, right, from, to); return part1 + part2 + part3 + part4 + part5; } }
将左、中、右三个地点抽象成栈,LS、MS、RS,那么栈的操做就能够当作是:某一个栈(from)把栈顶元素弹出,而后压入到另外一个栈里(to),做为另外一个栈(to)的栈顶。
游戏的第一个动做必定是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只会 有一个达标
如上,每一步只会有一个动做达标,那么只要每走一步都根据这两个原则考察全部的动做就能够,哪一个达标走哪一个。
public static enum Action { No, LToM, MToL, MToR, RToM } public static int hanoiProblem2(int num, String left, String mid, String right) { //左栈 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); //从num开始由大到小依次入左栈 for (int i = num; i > 0; i--) { lS.push(i); } Action[] record = { Action.No }; int step = 0; //若是右栈的个数不等于num+1说明尚未移动完 while (rS.size() != num + 1) { step += fStackTotStack(record, Action.MToL, Action.LToM, lS, mS, left, mid); step += fStackTotStack(record, Action.LToM, Action.MToL, mS, lS, mid, left); step += fStackTotStack(record, Action.RToM, Action.MToR, mS, rS, mid, right); step += fStackTotStack(record, Action.MToR, Action.RToM, rS, mS, right, mid); } return step; }