6、用栈来求解汉诺塔问题

初版题目

有左、中、右三根柱子,其中左柱子上面有从小叠到大的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的求解,使用栈比递归的求解步数多一半。(个人认为,递归求解是最优步骤)
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值