1-6 汉诺塔问题的改进

题目描述

  • 汉诺塔问题的改进。
  • 有三根杆子A,B,C。A杆上有N个(N>1)穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至C杆: 每次只能移动一个圆盘; 大盘不能叠在小盘上面。 提示:可将圆盘临时置于B杆,也可将从A杆移出的圆盘重新移回A杆,但都必须遵循上述两条规则。问具体应该怎样移动。
  • 以上是经典的汉诺塔问题,现在修改一下移动的规则,不能从a塔直接移动到c塔,也不能直接从c塔直接移动到a塔,而是必须要经过中间的b塔。打印最优移动步数,以及最优移动总步数。

解题方法1

  • 使用递归的方法解决。
  • 如果只剩一个盘子需要移动,可以分两种情况讨论:
    从塔a移动到塔b只需要一步,直接 a >> b 即可。从b到a,从b到c,从c到b同理只需要一步。
    从塔a移动到塔c需要两步, a >> b and b >> c。同理从c到a也需要两步。
  • 如果还剩k个盘子需要移动,仍然分两种情况讨论:
    把k个盘子从塔a移动到塔b,需要三步,把前k-1个盘子从a移到c(交给递归),把第k个盘子从a移到b,把前k-1个盘子从c移到b(交给递归)。对于把k个盘子从b到a,从b到c,从c到b同理需要三步。
    把k个盘子从a移到c,需要五步,把前k-1个盘子从a移到c,把第k个盘子从a移到b,把前k-1个盘子从c移到a,把第k个盘子从b移到c,把前k个盘子从a移到c。对于把k个盘子从c移到a同理需要五步。
  • 代码如下:(设盘子从上到下命名为1-n)
public class Test {
    public static void main(String[] args) {
        hannuo(2,'a','b','c');
    }
    //参数1:盘子个数
    //参数2:源塔
    //参数3:中间塔
    //参数4:目标塔
    public static void hannuo(int n,char from,char mid,char to){
         if(n==1){
             //移动两步的情况
             if((from=='a' && to=='c') || (from=='c' && to=='a')){
                 System.out.println("盘子"+n+":"+from +" >> " + mid);
                 System.out.println("盘子"+n+":"+mid +" >> " + to);
             }
             //移动一步的情况
             else{
                 System.out.println("盘子"+n+":"+from +" >> " + to);
             }
         }
         else{
             //移动五步的情况
             if((from=='a' && to=='c') || (from=='c' && to=='a')){
                 hannuo(n-1,from,mid,to);
                 System.out.println("盘子"+n+":"+from +" >> " + mid);
                 hannuo(n-1,to,mid,from);
                 System.out.println("盘子"+n+":"+mid +" >> " + to);
                 hannuo(n-1,from,mid,to);
             }
             //移动三步的情况
             else{
                hannuo(n-1,from,mid,to);
                System.out.println("盘子"+n+":"+from +" >> " + mid);
                hannuo(n-1,to,from,mid);
             }
         }
    }
}

解题方法2

  • 用栈的方式解决。
  • 修改后的汉诺塔不能从a直接到c,也不能从c直接到a,那么最基本的动作只有四个:a 到 b,b到a,b到c,c到b。
  • 我们可以把a b c三个塔抽象成三个栈,依次记为sa,sb,sc,最初所有的盘子都在sa上面。那么上面的四个基本动作就可以看做从一个栈出栈一个元素再压入到另一个栈中。
  • 而且基本动作有如下限制:不能把大盘子压在小盘子上面,相邻的动作不能可逆(这只会增加无用的步数),而且整个过程的第一步一定是从a到b。
  • 这样就可以推导出一个结论:在走出最小步长的过程中的任何一个时刻,这四个动作只有一个是合法的,其他三个都会违背小压大和相邻不可逆原则。
  • 如果上一步操作是a到b,那么下一步操作首先排除a到b和b到a,然后判断b和c谁的栈顶小把谁出栈压入另一个栈。
  • 所以只要按照上述规则,让程序从a到b开始,然后约束程序只能走合法动作,那么整个程序就会顺利执行结束得到最终结果。
  • 程序的终止条件是所有盘子都正确压入到栈c中,即栈c的长度为n。
public class Test {
    public static void main(String[] args) {
        hannuo(3,'a','b','c');
    }
    //参数1:盘子个数
    //参数2:源塔
    //参数3:中间塔
    //参数4:目标塔
    public static void hannuo(int n,char a,char b,char c){
        Stack<Integer> sa = new Stack<>();
        Stack<Integer> sb = new Stack<>();
        Stack<Integer> sc = new Stack<>();
        //进行初始化操作
        sa.push(Integer.MAX_VALUE);
        sb.push(Integer.MAX_VALUE);
        sc.push(Integer.MAX_VALUE);
        for(int i=n;i>=1;i--){
            sa.push(i);
        }
        String[] pre = {""}; //记录上一步的操作,初始化为空字符串
        while(sc.size()<n+1){
            //直接将四个基本步骤全部尝试执行,合法的步骤执行不合法的退出即可
            move(pre,"atob","btoa",sa,sb,a,b);
            move(pre,"btoa","atob",sb,sa,b,a);
            move(pre,"btoc","ctob",sb,sc,b,c);
            move(pre,"ctob","btoc",sc,sb,c,b);
        }
    }
    //判断一个操作是否合法,合法执行不合法退出
    public static void move(String[] pre, //参数1 记录上一步的操作
                            String now, //参数2 当前进行的操作
                            String renow, //参数3 当前操作的逆操作
                            Stack<Integer> sfrom, //参数4 源栈
                            Stack<Integer> sto, //参数5 目的栈
                            char from, //参数6 源塔
                            char to //参数7 目标塔
                          ){
         //如果上一步的操作等于当前操作或者等于当前操作的逆操作,以及源栈栈顶大于目标栈栈顶说明当前操作不合法
         if(pre[0].equals(now) || pre[0].equals(renow) || sfrom.peek()>sto.peek()){
             return;
         }
         //如果操作合法 执行操作 并把当前操作记录到上一步操作中 以便下一步操作使用
        System.out.println("盘子"+sfrom.peek()+":"+from+">>"+to);
        sto.push(sfrom.pop());
        pre[0] = now;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章