漢諾塔的遞歸學習,以及一些其中的一些規律


  啥也不廢話進入正題:

  首先是經典的解法,即遞歸,過程大概如下圖

 

  實現的代碼如下:

    int steps = 0;
    int [] sData = new int[]{1,2,3,4,5,6};
    int [] hData = new int[sData.length];
    int [] tData = new int[sData.length];
    @Test
    public void testHanoi(){
        long start = new Date().getTime();
        hanoi(sData.length,sData,hData,tData);
        long end = new Date().getTime();
        System.out.println(Arrays.toString(tData));
        System.out.println("共走了 :" + steps + "步。" + "花費時間:"+(end-start));
    }

        /**
     * 
     * @param n
     * @param sData : 待移動的集合
     * @param hData : 輔助空間 
     * @param tData : 目標集合
     */
    // n:編號,x:待移動的集合 y:輔助空間 z:目標集合
    private void hanoi(int n, int[] sData, int[] hData, int[] tData) {
        if(n == 1){
            move(1,sData,tData);
        }else{
            hanoi(n-1,sData,tData,hData);
            move(n , sData , tData );
            hanoi(n-1,hData,sData,tData);
        }
        
    }
    
    private void move(int i, int[] sData, int[] tData) {
        tData[i-1] = sData[i-1];
        sData[i-1] = 0;
        steps++;
        /*System.out.print("source:"+Arrays.toString(this.sData) + "   target:"+Arrays.toString(this.tData) +"   help:");
        System.out.print(Arrays.toString(this.hData)+"   ");
        System.out.println("將第"+i+"個方塊從 "+judgeName(sData)+" 移動到 "+judgeName(tData));*/
    }

    String judgeName(int []Data){
        if(Data == this.sData){
            return "source";
        }else if(Data == this.tData){
            return "target";
        }else if(Data == this.hData){
            return "help";
        }
        return "";
    }
    

  由於有強迫症,硬是要用腦子去遞歸這塊代碼,結果大腦溢出了。。。,於是跟着debug跟了一下,算是稍微有點點收獲(自己安慰自己),不管了,根據代碼的流程來總結總結是到底怎么換的。

  

 

 

                     上級遞歸不斷重復下級遞歸,每級遞歸都要走到最內層(最深層,方法棧最上層)遞歸才可以返回

 

  這里特別要關注的是,不同角色的空間,傳到下一級遞歸時候角色的轉變 , 所以返回上級遞歸,要根據入參規則轉換一下 , 不然還是很難理解返回的時候,參數的變換是什么意思,比如說上圖 n=3時候的遞歸場景,返回到 n=4的遞歸場景,target和help要交換一下角色,才是這級遞歸對應的角色(因為入參的時候,是交換角色入參的)。

  其實遞歸過程中最繞的就是這些參數角色之間的轉換,但是真正存儲的空間和含義(最外層遞歸定義的含義)只有一個,在調用自己的時候,可能會產生角色交換,所以導致大腦溢出。除了要搞清楚最原始定義的空間是怎么變化的,其他的還是很好理解的,遞歸嘛,感覺像是一種繼承性,像是不斷的 子承父業 子承父業 子承父業,直到斷子絕孫了,你就可以返回,一直返回到祖宗十八代,就玩完了。

 

一下介紹一下非遞歸的算法的思想,很固定的套路

非遞歸算法描述如下:

首先容易證明,當盤子的個數為n時,移動的次數應等於2^n - 1。

一位美國學者發現一種出人意料的方法,只要輪流進行兩步操作就可以了。

首先把三根柱子按順序排成品字型,把所有的圓盤按從大到小的順序放在柱子A上。

根據圓盤的數量確定柱子的排放順序:若n為偶數,按順時針方向依次擺放 A B C;

若n為奇數,按順時針方向依次擺放 A C B。

(1)按順時針方向把圓盤1從現在的柱子移動到下一根柱子,即當n為偶數時,若圓盤1在柱子A,則把它移動到B;

若圓盤1在柱子B,則把它移動到C;若圓盤1在柱子C,則把它移動到A。

(2)接着,把另外兩根柱子上可以移動的圓盤移動到新的柱子上。

即把非空柱子上的圓盤移動到空柱子上,當兩根柱子都非空時,移動較小的圓盤

這一步沒有明確規定移動哪個圓盤,你可能以為會有多種可能性,其實不然,可實施的行動是唯一的。

(3)反復進行(1)(2)操作,最后就能按規定完成漢諾塔的移動。

 

啥時候也能有蘋果光臨光臨咱們的腦瓜子呀。

實現代碼:

  
  int steps = 0 ;
  @Test
public void testHanoi2(){ LinkedList<Integer> source = new LinkedList<Integer>(); LinkedList<Integer> target = new LinkedList<Integer>(); LinkedList<Integer>help = new LinkedList<Integer>(); // 若source中有 偶數個元素 , 那么 , 最終全部轉換到第三個容器 ; 為奇數的話,全部轉換到第二個容器 LinkedList<Integer> [] datas = new LinkedList[3]; datas[0] = source; datas[1] = help; datas[2] = target; for(int i=5 ; i>0 ; i--){ source.push(i); } int count = source.size(); for(int j=0 ; target.size() != count ;j++){ int index = j%3; // 這個會是最終存儲所有方塊的容器的索引 int nexIndex =(index+1)%3 ; int otherIndex = (nexIndex+1)%3; datas[nexIndex].push(datas[index].pop()); steps++; if(datas[nexIndex].size() == count){ break; } if(datas[otherIndex].isEmpty() ){ datas[otherIndex].push(datas[index].pop()); }else if(datas[index].isEmpty()){ datas[index].push(datas[otherIndex].pop()); }else if(datas[index].peek()>datas[otherIndex].peek()){ datas[index].push(datas[otherIndex].pop()); }else if(datas[otherIndex].peek()>datas[index].peek()){ datas[otherIndex].push(datas[index].pop()); } steps++; } System.out.println(target); System.out.println(source); System.out.println(help); System.out.println("共花費:"+steps); }

以上純屬個人觀點,如有不對,還請大佬鞭策,謝謝大佬們


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM