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;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章