【棧】用棧來求解漢諾塔問題

題目描述

漢諾塔問題比較經典,這裏修改一下遊戲規則:現在限制不能從最左側的塔直接移動到最右側,也不能從最右側直接移動到最左側,而是必須經過中間。求當塔有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;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章