【算法筆記】使用棧實現漢諾塔(Hanoi)經典算法

漢諾塔(Hanoi)算法,應該是每一個程序員都會學習到的遞推算法之一,漢諾塔是一個很著名的智力題,但是這裏就不科普它的由來了,我們直接進入正題。
這裏寫圖片描述
      如上圖,假設A棒有五個原盤,依次移動,每次移動一塊,小的永遠只能在上面,最終移動到C棒上,如何用算法實現呢?
      從這裏移動的邏輯我們很容易發現,A幫不就像一個棧嗎,棧頂必須先出,網上看過很多漢諾塔算法,很少涉及到用棧實現,的確,算法拿出來了,用什麼都一樣,在我學習的時候,教材上是用的char,直接模擬推算,沒用真正移動數據實現真正的Hanoi思想,所以,琢磨了一會,寫了一個用棧實現的算法。
      首先,既然是棧,爲了方便跟蹤,寫了一個自己的MyStack包裝了一下Java的Stack,貼上代碼:

class MyStack{
    private String name;
    private Stack<Integer> data;
    public MyStack(String name){
        this.name=name;
        data=new Stack<>();
    }
    public String getName(){
        return this.name;
    }
    public void push(int data){
        if(!this.data.isEmpty()&&this.data.peek()<data){
            System.out.println("出錯");
        }
        this.data.push(data);
    }
    public int peek(){
        return data.peek();
    }
    public int pop(){
        return data.pop();
    }
    public int size(){
        return data.size();
    }
    public boolean isEmpty(){
        return data.isEmpty();
    }
}

      然後就是Hanoi遞推的實現,還是貼上圖片
這裏寫圖片描述

      我們要按照Hanoi的邏輯將原盤從A棒移動到C棒,那麼,就必須以B棒作爲媒介,
最大的必須在最下面,所以,我們必須把上面4個圓盤先移動到B棒上。
      但是又看,上面4個,要想把第四個移動到B棒,就得用C棒暫時當媒介,先把上面3個移動到C棒,以此類推,知道只有最後一個的時候,就可以直接移動了,所以,我們要做的就是想出一個算法,遞推到只剩一個圓盤,然後慢慢回棧,到第二,第三,第四,最後第五。
先貼上代碼:

public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
        if(size==1){
            c.push(a.pop());
        }else{
            int n=b.size();
            hanoi(size-1,a,c,b);
            c.push(a.pop());
            hanoi(b.size()-n,b,a,c);
        }
    }

      首先解釋參數中的size,因爲棧無法在不取出元素的情況下遞減長度,所以增加了參數size作爲棧的圓盤指針,限定只能移動size個圓盤。

      所以,當size==1的時候,就是只剩下一個圓盤,那麼就順理成章的直接移動到C棒了,不必在意此時的C棒回棧後是B棒還是A棒,那不是此時遞歸該擔心的事情。

      在算法中,如果size!=1,那麼說明我們需要一個作爲媒介,讓size-1個圓盤先暫時放到媒介上,然後將第size個圓盤放過去,所以,我們需要進行一次遞歸,將第size-1個上面的圓盤重新進行計算該存放的位置,計算完成後,然後放入第size個圓盤到C幫,然後再將媒介B棒中的圓盤又以A棒爲媒介,以此方式放入C盤。

      至於爲什麼要在遞歸前緩存一次B棒的size,因爲進入遞歸前不知道B棒是否有數據,說不定此次計算正是上一次的遞歸呢,不知道後面的方法B棒會是怎樣的存在,不知道會進入多少次遞歸,假設B棒在進入第一次遞歸前長度爲2,遞歸完後,長度爲5,第二次遞歸時,如果不限制size長度,直接使用B棒的size,那麼,除了第一次遞歸時增加的3個數據,還會把原本的2個數據一起計算進去,博主就在這個坑繞了一些時間,最後跟蹤了一下才明白這個。

文字有點多,最後貼上完整代碼:

public class Hanoi {
    private static int m=1;
    private static MyStack a=new MyStack("A");
    private static MyStack b=new MyStack("B");
    private static MyStack c=new MyStack("C");  
    public static void main(String[] args) {
        for(int i=5;i>0;i--){
            a.push(i);
        }
        hanoi(a.size(),a,b,c);
        print(c);
    }
    public static void hanoi(int size,MyStack a,MyStack b,MyStack c){
        if(size==1){
            System.out.println("第"+m+++"步,從"+a.getName()+"移動了  "+a.peek()+"到了"+c.getName());
            c.push(a.pop());
        }else{
            int n=b.size();
            hanoi(size-1,a,c,b);
            System.out.println("第"+m+++"步,從"+a.getName()+"移動了  "+a.peek()+"到了"+c.getName());
            c.push(a.pop());
            hanoi(b.size()-n,b,a,c);
        }
    }

    public static void print(MyStack temp){
        System.out.println("size:"+temp.size());
        for(int i=0,n=temp.size();i<n;i++){
            System.out.print(temp.pop()+" ");
        }
        System.out.println();
    }
}
class MyStack{
    private String name;
    private Stack<Integer> data;
    public MyStack(String name){
        this.name=name;
        data=new Stack<>();
    }
    public String getName(){
        return this.name;
    }
    public void push(int data){
        if(!this.data.isEmpty()&&this.data.peek()<data){
            System.out.println("出錯");
        }
        this.data.push(data);
    }
    public int peek(){
        return data.peek();
    }
    public int pop(){
        return data.pop();
    }
    public int size(){
        return data.size();
    }
    public boolean isEmpty(){
        return data.isEmpty();
    }
}

核心算法只有那一段,其他是我爲了方便跟蹤增加的,各位在測試的時候可以選擇性刪除。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章