【栈】用栈来求解汉诺塔问题

207 阅读2分钟

题目描述

汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有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;
    }