汉诺塔的递归学习,以及一些其中的一些规律

  啥也不废话进入正题:

  首先是经典的解法,即递归,过程大概如下图

 

  实现的代码如下:

    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); }

以上纯属个人观点,如有不对,还请大佬鞭策,谢谢大佬们

posted on 2017-10-28 01:21  刘子哥  阅读(4001)  评论(0编辑  收藏  举报

导航