• 算法14用栈来求解汉诺塔问题


    描述

    汉诺塔问题比较经典,这里修改一下游戏规则:现在限制不能从最左侧的塔直接移动到最右侧,也不能从最右侧直接移动到最左侧,而是必须经过中间。求当塔有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);
        }
    }
  • 相关阅读:
    TreeView控件
    俄罗斯套娃
    c#文件操作
    c# 操作excle
    vs2010启动越来越慢解决方法
    c# 操作excle[转]
    c# 命名空间别名
    C# openfiledialog的使用
    c# 获取项目根目录方法
    jquery操作复选框(checkbox)的12个小技巧总结
  • 原文地址:https://www.cnblogs.com/sfnz/p/15802366.html
Copyright © 2020-2023  润新知