簡析遞歸算法

簡析遞歸

遞歸是編程過程中比較重要,也是比較簡單的算法之一,簡單的遞歸思路,往往比較容易理解,而略微複雜的遞歸則容易讓人頭痛。

遞歸主要分兩種

  1. 直接遞歸
    直接遞歸函數裏面調用本身這個函數,實現遞歸
  2. 間接遞歸。
    間接遞歸是兩個,或兩個以上的函數互相調用,正好形成一個循環,例如:A調用B,而B又調用A,在某種條件下,會實現間接遞歸。自然也可以是多個函數組成間接遞歸,不過考慮到邏輯問題,一般都不會這麼繞。

直接遞歸

public int Factorial( n)
{
if(n==0||n==1)
return 1;
else
return n * Factorial(n-1);
}

這是一個直接遞歸求階乘的例子,自然求階乘用遞歸不是一個好的方法,完全可以用循環來代替。由於這是一個單層次的問題,而用遞歸把每一次階乘都當作一個層次,而每一層都只有個元素,有些得不償失

看一下這個遍歷文件夾的例子

    public static void getFileDir(File file){
        //如果是文件夾,那就是直接遞歸,如果不是那就直接輸出文件名
        if(file.isDirectory()) {
            File[] files=file.listFiles();
            for(int i=0;i<files.length;i++){
                main.getFileDir(files[i]);
            }
        }
        else{
            System.out.println(file.getAbsolutePath());
        }
    }

思路相當的簡單,但是你只要給一個文件夾目錄,就可以遍歷出這個文件夾下面的所有文件名,而每一層都是一個目錄,同一層下有多個元素,就像一顆樹一樣。

遍歷的思想就體現出來了,樹形結構的遍歷不就是遞歸嗎!而線性結構的遍歷就是循環.

說一下遞歸的深度問題,有時候需要獲得遞歸的深度,好分組,像在爬蟲中就是使用遞歸的思想,但是如何分類呢?沒錯,就是根據深度來分,但如何知道這是第幾層呢(深度)。來讓我們小改一下程序

    public static void getFileDir(File file,int deep){
        if(file.isDirectory()) {
            File[] files=file.listFiles();
            for(int i=0;i<files.length;i++){
                main.getFileDir(files[i],deep+1);
            }
        }
        else{
            System.out.println(file.getAbsolutePath()+"    深度爲:"+deep);
`
    }
    }

沒錯僅僅加了一個參數,deep,傳入1就可以了,多麼簡單,第二層輸出的是deep+1,第三層就是((deep+1)+1),那麼第一層的deep會變嗎?沒有變化!第二層永遠都會是二,我們沒有對deep做過賦值,只是改變傳入的參數而已(在前面遇到有不理解此問題的朋友,所有特意說明一下)

接下來看一下間接遞歸的例子

var  time = 24*60*60*2; //倒計時兩天的時間,自己設定
            //輸出信息
            function begin(){
                var today=new Date()
                var day =today.getDate()
                var dat=today.getMonth()
                var future=day+2
                document.getElementById('now').innerHTML="現在時間"+dat+"月"+day+"倒計時開始"

                leasttime()

                document.getElementById("future").innerHTML="預計結束時間"+dat+"月"+future

            }
            //時間倒計時函數
            function leasttime(){

                var  ho=time/(60*60);
                var  mi=(time/60)%(60)
                var  se=time%60
                mi=parseInt(mi)
                ho=parseInt(ho)
                ho=checkTime(ho)
                se=checkTime(se)
                mi=checkTime(mi)
                time-=1;
                document.getElementById("last").innerHTML=ho+":"+mi+":"+se
                //倒計時結束
                if(time==0){
//              //重置計時器 ,再次開始計時
                time=30;
                begin()
                }
                setTimeout("leasttime()",1000);
            }
            //將時間的格式轉化一下
            function checkTime(i)
            {
                if (i<10) 
                  {i="0" + i}
                  return i
            }

這是一個使用javascript完成的一個循環倒計時的計時器,由於在javascript中是解釋執行的,而且由於在主線程中,無法使用死循環執行循環倒計時的效果,瀏覽器會卡死並報錯。

總結一下遞歸的構成條件

  1. 子問題須與原始問題爲同樣的事,且更爲簡單;
  2. 不能無限制地調用本身,須有個出口,化簡爲非遞歸狀況處理。

遞歸的使用
1 遍歷樹形結構
2. 像javascript中間接遞歸的特例。
遞歸算法一般用於解決三類問題:
1.數據的定義是按遞歸定義的。(Fibonacci函數)
2.問題解法按遞歸算法實現。
這類問題雖則本身沒有明顯的遞歸結構,但用遞歸求解比迭代求解更簡單,如Hanoi問題。
3.數據的結構形式是按遞歸定義的。
如二叉樹、廣義表等,由於結構本身固有的遞歸特性,則它們的操作可遞歸地描述。
遞歸的缺點:
遞歸算法解題相對常用的算法如普通循環等,運行效率較低。因此,應該儘量避免使用遞歸,除非沒有更好的算法或者某種特定情況,遞歸更爲適合的時候。在遞歸調用的過程當中系統爲每一層的返回點、局部量等開闢了棧來存儲。遞歸次數過多容易造成棧溢出等。

如何設計遞歸算法
1.確定遞歸公式
2.確定邊界(終了)條件
練習:
用遞歸的方法完成下列問題
1.求數組中的最大數
2.1+2+3+…+n
3.求n個整數的積
4.求n個整數的平均值
5.求n個自然數的最大公約數與最小公倍數
6.有一對雌雄兔,每兩個月就繁殖雌雄各一對兔子.問n個月後共有多少對兔子
7.已知:數列1,1,2,4,7,13,24,44,…求數列的第 n項.
練習:
1.計算ackerman函數值:
n+1 m=0
ack(m,n)={ ack(m-1,1) m<>0,n=0
ack(m-1,ack(m,n-1)) m<>0,n<>0
求ack(5,4)

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