初版题目
有左、中、右三根柱子,其中左柱子上面有从小叠到大的n个圆盘,现要求将左柱子上的圆盘移动到右柱子上,期间只有一个原则:一次只能移动一个盘子且大盘子不能在小盘子上面,求移动的步骤和移动的次数。
解法
采用递归方法,递归就要将部分问题以整体的形式看待,具体思路见注释。
// 如果n=1,直接将盘子转移到目标柱即可
// 如果n>1,则汉诺塔问题必经过三个状态,分别是:
// 1.将n-1个盘子从起始柱转移到辅助柱
// 2.将第n个盘子从起始柱转移到目标柱
// 3.将n-1个盘子从辅助柱转移到目标柱
public static void hanoi(Integer n, String start, String help, String end) {
if(n == 1){
System.out.println("disk" + n + " " + start + ">" + end);
}else{
hanoi(n - 1, start, end, help);
System.out.println("disk" + n + " " + start + ">" + end);
hanoi(n - 1, help, start, end);
}
}
进阶题目
在初版题目的基础上,修改一下游戏规则:现在限制不能从最左侧的柱子直接移动到右侧的柱子上,也不能直接从右侧的柱子上直接移动到左侧,必须经过中间。
解法
1、递归
思路跟原题目类似,但是移动类型变得复杂一些。
public class SeniorHanoiByRecursion {
public int seniorHanoiByRecursion(int num, String left, String mid, String right){
if(num < 1) {
System.out.println("No disk!");
return 0;
}
return process(num, left, mid, right, left, right);
}
public static int process(int n, String left, String mid, String right, String from, String to) {
if(n == 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;
}
}else{
if(from.equals(mid) || to.equals(mid)){
String another = (from.equals(left) || to.equals(left)) ? right : left;
int part1 = process(n-1, left, mid, right, from, another);
int part2 = 1;
System.out.println("Move " + n + " from " + from + " to " + to);
int part3 = process(n-1, left, mid, right, another, to);
return part1 + part2 + part3;
}else{
int part1 = process(n-1, left, mid, right, from, to);
int part2 = 1;
System.out.println("Move " + n + " from " + from + " to " + mid);
int part3 = process(n-1, left, mid, right, to, from);;
int part4 = 1;
System.out.println("Move " + n + " from " + mid + " to " + to);
int part5 = process(n-1, left, mid, right, from, to);
return part1 + part2 + part3 + part4 + part5;
}
}
}
}
2、栈
4类移动轨迹,可以根据不可逆与移动限制条件筛选唯一的解,得到最终的解。
import java.util.Stack;
public class SeniorHanoiByStack {
public enum Action{
No, LToM, MToL, MToR, RToM
}
public Integer hanoiByStack(Integer 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);
for(int i = num; i > 0; i--){
ls.push(i);
}
//一开始无,后边这里记录上一步的动作,可以理解为一个全局变量,因为java没有引用型,这样用的。
Action[] record = {Action.No};
int step = 0;
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.MToR, Action.RToM, rs, ms, right, mid);
step += fStackTotStack(record, Action.RToM, Action.MToR, ms, rs, mid, right);
}
return step;
}
public static int fStackTotStack(Action[] record, Action preNoAct, Action nowAct, Stack<Integer> fStack, Stack<Integer> tStack, String from, String to){
if(record[0] != preNoAct && fStack.peek() < tStack.peek()){
tStack.push(fStack.pop());
System.out.println("Move " + tStack.peek() + " from " + from + " to " + to);
record[0] = nowAct;
return 1;
}
return 0;
}
}
测试
public class a1_b6_seniorHanoi {
public static void main(String[] args) {
//通过递归的方式进行汉诺塔移动
SeniorHanoiByRecursion seniorHanoiByRecursion = new SeniorHanoiByRecursion();
int steps1 = seniorHanoiByRecursion.seniorHanoiByRecursion(3, "left", "mid", "right");
System.out.println("It needs " + steps1 + " steps!");
System.out.println("-----------------------------------------");
//通过栈的方式进行汉诺塔移动
SeniorHanoiByStack seniorHanoiByStack = new SeniorHanoiByStack();
int steps2 = seniorHanoiByStack.hanoiByStack(3, "left", "mid", "right");
System.out.println("It needs " + steps2 + " steps!");
}
}
结果:
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
Move 3 from left to mid
Move 1 from right to mid
Move 1 from mid to left
Move 2 from right to mid
Move 1 from left to mid
Move 1 from mid to right
Move 2 from mid to left
Move 1 from right to mid
Move 1 from mid to left
Move 3 from mid to right
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 needs 26 steps!
-----------------------------------------
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
Move 3 from left to mid
Move 1 from right to mid
Move 1 from mid to left
Move 2 from right to mid
Move 1 from left to mid
Move 1 from mid to right
Move 2 from mid to left
Move 1 from right to mid
Move 1 from mid to left
Move 3 from mid to right
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 needs 26 steps!
Process finished with exit code 0
思考
可以尝试写一下,原始问题通过栈的求解代码:
public enum Action{
No,LToM,LToR,MToL,MToR,RToL,RToM
}
public static int hanoiByStack(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);
for(int i = num; i > 0; i--){
ls.push(i);
}
Action[] record = {Action.No};
int step = 0;
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.LToR, Action.RToL, rs, ls, right, left);
step += fStackTotStack(record, Action.RToL, Action.LToR, ls, rs, left, right);
step += fStackTotStack(record, Action.MToR, Action.RToM, rs, ms, right, mid);
step += fStackTotStack(record, Action.RToM, Action.MToR, ms, rs, mid, right);
}
return step;
}
private static int fStackTotStack(Action[] record, Action preNoAct, Action nowAct, Stack<Integer> fStack, Stack<Integer> tStack, String from, String to) {
if(record[0] != preNoAct && fStack.peek() < tStack.peek()){
tStack.push(fStack.pop());
System.out.println("disk" + tStack.peek() + " " + from + ">" + to);
record[0] = nowAct;
return 1;
}
return 0;
}
经过实验验证,发现原汉诺塔问题的用栈的求解对于大多数num来说,往往与递归求解结果不同,所以得到两个结论。
- 原始汉诺塔问题的求解答案有多种,进阶汉诺塔问题的求解答案只有一种
- 原始汉诺塔问题,对于一些num的求解会相同;对于一些num的求解,使用栈比递归的求解步数多一半。(个人认为,递归求解是最优步骤)