【刷题 issue6】程序员代码面试指南 —— IT 名企算法与数据结构题目最优解

第一章 栈和队列

1.6 用栈来求解汉诺塔问题

【题目】

汉诺塔问题比较经典,这里修改一下游戏规则:限制不能从最左侧的塔直接移动到最右侧,也不从最右侧直接移动到最左侧,必须经过中间。求当塔有 N 层的时候,打印最优移动过程和最优移动总步数。

【要求】

使用一下两种方法解决:

  • 方法一:递归的方法;
  • 方法二:非递归的方法,用栈模拟整个过程。

【难度】

校 ★★★☆

【题解】

方法一:【递归】

  1. 首先,如果只剩最上层的塔需要移动,则可能有以下过程:
  2. 从 “左” 移动到 “中”;
  3. 从 “中” 移动到 “左”;
  4. 从 “中” 移动到 “右”;
  5. 从 “右” 移动到 “中”;
  6. 从 “左” 移动到 “右”;
  7. 从 “右” 移动到 “左”;
  8. 以上是递归的终止条件,也就是只剩下最上层塔的移动过程。
  9. 接下来分析剩下多层塔的情况。
  10. 如果剩下 N 层塔,从上到下一次是 1 ~ N,则有如下过程:
  11. 如果剩下的 N 层塔都在 “左”,全部移到 “中”,有三个步骤:
  12. 将 1 ~ N-1 层塔先全部从 “左” 移动到 “右”,交给递归完成;
  13. 将第 N 层塔从 “左” 移动到 “中”;
  14. 再将 1 ~ N-1 层塔全部从 “右” 移动到 “中”,交给递归完成。
  15. 如果把剩下的 N 层塔从 “中” 移动到 “左”,从 “中” 移动到 “右”,从 “右” 移动到 “中”,过程与 a 类似似,同样分为三步完成,这里不再赘述。
  16. 如果剩下的 N 层塔都在 “左”,全部移动到 “右”,右五个步骤:
  17. 将 1 ~ N-1 层塔先全部从 “左” 移动到 “右”,交给递归完成;
  18. 将第 N 层塔从 “左” 移动到 “中”;
  19. 将 1 ~ N-1 层塔全部从 “右” 移动到 “左”,交给递归完成;
  20. 将第 N 层塔从 “中” 移动到 “右”;
  21. 最后将 1 ~ N-1 层塔全部从 “左” 移动到 “右”,交给递归完成;
  22. 如果剩下的 N 层塔从 “右” 移动到 “左”,过程与 c 类似,同样分为五步完成,不在赘述。

方法二:【非递归】

  1. 修改后的汉诺塔问题不能让任何塔直接从 “左” 移动到 “右”,也不能直接从 “右” 移动到 “左”,必须经过中间。这样实际的动作只有四个:
  2. 从 “左” 到 “中”
  3. 从 “中” 到 “左”
  4. 从 “中” 到 “右”
  5. 从 “右” 到 “中”
  6. 把左、中、右三个位置抽象为栈,记为 LS、MS 和 RS。最开始所有的塔都在 LS 上。那么以上四个动作可以看作为:某一个栈 (from)弹出栈顶元素,然后压入另一个栈(to)中。
  7. 这种实现需要不能违反两个原则:
  8. 一个动作能发生的先决条件是不违反小压大的原则:即栈 from 栈顶弹出的元素 num 如果要压入栈 to 中,那么 num 的值必须小于栈 to 栈顶元素。
  9. 还不能违反相邻不可逆原则:
  10. 把四个动作定义为 L2M、M2L、M2R 和 R2M;
  11. L2M 和 M2L 互为逆过程,M2R 和 R2M 同样互为逆过程;
  12. 在汉诺塔游戏中,如果得到最少步数,那么任何两个相邻的动作都不是互为逆过程的。
  13. 为了满足小压大和相邻不可逆原则,必有如下结论:
  14. 游戏的第一个动作一定是 L2M;
  15. 任一过程中,四个动作中只会有一个动作不违反原则,另外三个一定会违反。
  16. 综上所述,每一步只会有一个动作满足条件,那么每走一步可以根据这两个原则来判断下一步发生的动作。

【实现】

  • Hanoi.java
import java.util.Objects;
import java.util.Stack;

public class Hanoi {

    private Hanoi() {}

    public static int recursionImpl(int num, final String left, final String mid, final String right) {
        if (num < 1) {
            return 0;
        }
        return recursion(num, left, mid, right, left, right);
    }

    private static int recursion(int num, final String left, final String mid, final String right, String from, String to) {
        if (Objects.equals(from, to)) {
            return 0;
        }
        if (num == 1) {
            if (Objects.equals(from, mid) || Objects.equals(to, 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 (Objects.equals(from, mid) || Objects.equals(to, mid)) {
                String another = (Objects.equals(from, left) || Objects.equals(to, left)) ? right : left;
                int step1 = recursion(num - 1, left, mid, right, from, another);
                int step2 = 1;
                System.out.println("Move 1 from " + from + " to " + to);
                int step3 = recursion(num - 1, left, mid, right, another, to);
                return step1 + step2 + step3;
            } else {
                int step1 = recursion(num - 1, left, mid, right, from, to);
                int step2 = 1;
                System.out.println("Move 1 from " + from + " to " + mid);
                int step3 = recursion(num - 1, left, mid, right, to, from);
                int step4 = 1;
                System.out.println("Move 1 from " + mid + " to " + to);
                int step5 = recursion(num - 1, left, mid, right, from, to);
                return step1 + step2 + step3 + step4 + step5;
            }
        }

    }

    private static enum Action {
        NO, L2M, M2L, M2R, R2M
    }

    public static int stackImpl(int num, final String left, final String mid, final String right) {
        final Stack<Integer> LS = new Stack<>();
        final Stack<Integer> MS = new Stack<>();
        final Stack<Integer> RS = new Stack<>();
        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 += move(record, Action.M2L, Action.L2M, LS, MS, left, mid);
            step += move(record, Action.L2M, Action.M2L, MS, LS, mid, left);
            step += move(record, Action.R2M, Action.M2R, MS, RS, mid, right);
            step += move(record, Action.M2R, Action.R2M, RS, MS, right, mid);
        }
        return step;
    }

    private static int move(Action[] pre, Action no, Action now, final Stack<Integer> FS, final Stack<Integer> TS, String from, String to) {
        if (!Objects.equals(pre[0], no) && FS.peek() < TS.peek()) {
            TS.push(FS.pop());
            System.out.println("Move 1 from " + from + " to " + to);
            pre[0] = now;
            return 1;
        }
        return 0;
    }

}
public class HanoiTest {

    public static void main(String[] args) {
        final String left = "left";
        final String mid = "mid";
        final String right = "right";
        int num = 5;
        Hanoi.recursionImpl(num, left, mid, right);
        System.out.println("————————————————————————————————————————");
        Hanoi.stackImpl(num, left, mid, right);
    }

}
发布了147 篇原创文章 · 获赞 72 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Pranuts_/article/details/100162686