描述

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

输入描述:

输入一个数n,表示塔层数

输出描述:

按样例格式输出最优移动过程和最优移动总步数

示例

输入:
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:递归的方法;

方法2:非递归的方法,用栈来模拟汉诺塔的三个塔;

 

方法1:递归的方法

先来看看一般的汉诺塔问题,A,B,C三个柱子,将A柱子上的圆盘移动到C上面,但是要保持小盘必须落在大盘上面才可以直接移动,并且一次只能移动一个圆盘。通过递归求解,如果n-1个盘子已经移动到B,那么n直接移动到C,然后将n-1从B移动到C即可。

  1)、将n-1个圆盘从A移动到B,借助C    hanota(n-1, A, C, B)

  2)、将n从A移动到C                               move(n, A, C)

  3)、将n-1圆盘从B移动都C,借助A       hanota(n-1, B, A, C)

代码如下:

import java.util.Scanner;
public class Hanota {
 
    public static void move(int n, String from, String to){
        System.out.println("将"+ n + "从" + from + "移动到" + to);
    }
 
    public static void hanota(int n, String A, String B, String C){
        if(n == 1){
            move(n, A, C);
        }else{
            hanota(n-1, A, C, B);
            move(n, A, C);
            hanota(n-1, B, A, C);
        }
    }
 
    public static void main(String
[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        hanota(n, "A", "B", "C");
    }
}

 

现在多了限制,就是移动必须经过中间的B,不能直接从A到C或者从C到A,那步骤修改一下:

  1)、将n-1个圆盘从A移动到C,借助B hanota(n-1, A, B, C)

  2)、将n从A移动到B                          move(n-1, A, B)

  3)、将n-1个圆盘从C移动到A,借助B hanota(n-1, C,B,A)

  4)、将n从B移动到C                          move(n, B, C)

  5)、将n-1个圆盘从A移动到C,借助B hanota(n-1, A, B, C)

代码如下:

import java.util.Scanner;

public class Main{
    
    
    static private int count = 0;
    
    public static void move(int n,String from,String to){
        System.out.println("Move " + n + " from " + from + " to " + to);
        count++;
    }
    
    public static void hanota(int n,String A,String B,String C){
        if(n == 1){
            move(n,A,B);
            move(n,B,C);
            return;
        }else{
            hanota(n-1,A,B,C);
            move(n,A,B);
            hanota(n-1,C,B,A);
            move(n,B,C);
            hanota(n-1,A,B,C);
        }
    }
    
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        hanota(n,"left", "mid", "right");
        System.out.println("It will move " + count + " steps.");
    }
    
}

方法2:非递归的方法,用栈来模拟汉诺塔的三个塔

  修改后的汉诺塔问题不能让任何塔从左直接移动到右,也不能从右直接移动到左,而是要经过中间,也就是说,实际上能做的动作,只有四个:左->中,中->左,中->右,右->中

  用栈来模拟汉诺塔的移动,其实就是某一个栈弹出栈顶元素,压入到另一个栈中,作为另一个栈的栈顶,理解了这个就好说了,对于这个问题,有两个原则:

  一:小压大原则,也就是,要压入的元素值不能大于要压入的栈的栈顶的元素值,这也是汉诺塔的基本规则;

  二:相邻不可逆原则,也就是,我上一步的操作如果是左->中,那么下一步的操作一定不是中->左,否则就相当于是移过去又移回来;

  有了这两个原则,就可以推导出两个非常有用的结论:

  1、游戏的第一个动作一定是L->M;

  2、在走出最小步数过程中的任何时刻,四个动作中只有一个动作不违反小压大和相邻不可逆原则,另外三个动作一定都会违反;

 

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

public class Main{
    
    static enum Action{
        No,LToM,MToL,MToR,RToM
    }
    
    public static int fStackToStack(Action[] record,Action preNoAct,Action nowAct,Stack<Integer> fStack, Stack<Integer> tStack,
                                   String from, String to, StringBuilder sb){
        if(record[0] != preNoAct && fStack.peek() < tStack.peek()){
            tStack.push(fStack.pop());
            sb.append("Move " + tStack.peek() + " from " + from + " to " + to).append("\n");
            record[0] = nowAct;
            return 1;
        }
        return 0;
    }
    
    public static int hanoiProblem(int num,String left,String mid,String right,StringBuilder sb){
        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 += fStackToStack(record, Action.MToL, Action.LToM, lS, mS, left, mid, sb);
            step += fStackToStack(record, Action.LToM, Action.MToL, mS, lS, mid, left, sb);
            step += fStackToStack(record, Action.RToM, Action.MToR, mS, rS, mid, right, sb);
            step += fStackToStack(record, Action.MToR, Action.RToM, rS, mS, right, mid, sb);
        }
        sb.append("It will move " + step + " steps.");
        System.out.println(sb);
        return step;
    }
    
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        String left = "left";
        String mid = "mid";
        String right = "right";
        StringBuilder sb = new StringBuilder();
        hanoiProblem(n,left,mid,right,sb);
    }
}