受限汉诺塔问题

这个问题是在《程序员代码面试指南》【书籍网址:https://www.nowcoder.com/tutorial/10016/index】上看到的,书上的解法思路有点繁琐了,从经典汉诺塔问题出发,我找到了一种更清晰的思路、更简洁的代码实现。

经典 汉诺塔 问题中,有 3 根柱子及 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.

经典汉诺塔问题

先从经典的汉诺塔问题看起

一阶汉诺塔过程

在这里插入图片描述

二阶汉诺塔过程

在这里插入图片描述

三阶汉诺塔过程

看左边的初始状态,因为 2 必须在 C 柱的最底端,所以首先的任务要把 0,1 移动到 B 柱上**【在ACB的顺序下执行二阶诺塔的移法 】**

在这里插入图片描述

再把 2 移动到 C 柱上【即把最大的圆盘从A移动到C

在这里插入图片描述

再把 0,1 移动到 C 柱上【即在BAC的顺序下执行二阶汉诺塔的移法

在这里插入图片描述

推广到任意N阶汉诺塔问题,都可以拆解为三个步骤:

  • 在ACB的顺序下执行 N-1 阶汉诺塔的移法
  • 把最大的圆盘从 A 移动到 C
  • 在BAC的顺序下执行了 N-1 阶汉诺塔的移法

题目链接:https://leetcode-cn.com/problems/hanota-lcci/

class Solution {
    public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
        process(A, B, C, A.size());
    }

    public void process(List<Integer> A, List<Integer> B, List<Integer> C, int N) {
        if (N == 0) return;
        process(A, C, B, N - 1);
		C.add(A.remove(A.size()-1));
        process(B, A, C, N - 1);
    }
}

用栈实现,打印移动过程

import java.util.Stack;

public class Main {
    public static Stack<Integer> A = new Stack<>() {{
        push(2); push(1); push(0);
    }};
    public static Stack<Integer> B = new Stack<>();
    public static Stack<Integer> C = new Stack<>();

    public static void main(String[] args) {
        process(A, B, C, A.size());
    }

    public static void process(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C, int N) {
        if (N == 0) return;
        process(A, C, B, N - 1);
        System.out.printf("Move %d from %c to %c\n", A.peek(), getName(A), getName(C));
        C.push(A.pop());
        process(B, A, C, N - 1);
    }

    public static Character getName(Stack<Integer> S) {
        //需要判断的是引用地址是否相等,这里要用==号来判断,不能用equals
        if (S == A) return 'A';
        else if (S == B) return 'B';
        return 'C';
    }
}

受限汉诺塔问题

三个步骤变为两个大步骤(五个小步骤):

用编号 1 , 2 , 3 , ⋯   , N 1,2,3,\cdots ,\rm N 1,2,3,,N 分别代指 N \rm N N 个盘子

在这里插入图片描述

  • N \rm N N 号盘子(最底下的)从 A 移动到 C
    • 把 A 柱上面 N − 1 \rm N-1 N1 个盘子移动到 C【即以 ABC 顺序执行 N − 1 \rm N-1 N1 阶汉诺塔】
    • N \rm N N 号盘子从A移动到B
    • 把 C 柱上的 N − 1 \rm N-1 N1 个盘子移动到 A【即以 CBA 顺序执行 N − 1 \rm N-1 N1 阶汉诺塔】
    • N \rm N N 号盘子从 B 移动到C
  • 把 A 柱上的 N − 1 \rm N-1 N1 个盘子移动到 C 柱上【即以 ABC 顺序执行 N − 1 \rm N-1 N1 阶汉诺塔】

所以核心代码与经典汉诺塔类似,书中给出的思路和代码有点繁琐了

public static void process(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C, int N) {
    if (N == 0) return;
    process(A, B, C, N - 1);
    B.push(A.pop());
    process(C, B, A, N - 1);
    C.push(B.pop());
    process(A, B, C, N - 1);
}

题目链接:https://www.nowcoder.com/ta/programmer-code-interview-guide 【用栈来求解汉诺塔问题】

import java.util.Scanner;
import java.util.Stack;

public class Main {
    public static Stack<Integer> A = new Stack<>(), B = new Stack<>(), C = new Stack<>();
    public static int counter = 0;

    public static void main(String[] args) {
        Scanner S = new Scanner(System.in);
        int N = S.nextInt();
        while (N > 0) {
            A.push(N--);
        }
        process(A, B, C, A.size());
        System.out.printf("It will move %d steps.", counter);
    }

    public static void process(Stack<Integer> A, Stack<Integer> B, Stack<Integer> C, int N) {
        if (N == 0) return;
        process(A, B, C, N - 1);
        counter++;
        System.out.printf("Move %d from %s to %s\n", A.peek(), getString(A), getString(B));
        B.push(A.pop());
        process(C, B, A, N - 1);
        counter++;
        System.out.printf("Move %d from %s to %s\n", B.peek(), getString(B), getString(C));
        C.push(B.pop());
        process(A, B, C, N - 1);
    }

    public static String getString(Stack<Integer> S) {
        if (S == A) return "left";
        else if (S == B) return "mid";
        return "right";
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值