递归资料
(很用的關于遞歸的講解:)
| 淺析程序設計中的遞歸算法熱 |
| [ 作者:晏素芹?|?轉貼自:本站原創?|?點擊數:1116?|?更新時間:2011-3-15?|?文章錄入:imste???2010年?第?17?期 ] |
| (徐州市廣播電視大學,江蘇 徐州 221006) 遞歸在計算機科學和數學中是一個很重要的工具, 它在程序設計語言中用來定義句法, 在 數據結構中用來解決表或樹形結構的搜索和排序等問題。另外,遞歸在計算方法、運籌學模 型、行為策略和圖論的研究中都得到了廣泛的應用。 若一個對象部分地包含它自己,或用它自己給自己定義, 則稱這個對象是遞歸的;在程序設 計中,若一個過程直接地或間接地調用自己,則稱這個過程是遞歸的過程。在定義一個過程 或函數時出現了調用本過程或函數的成分, 即調用自己本身,稱之為直接遞歸,若過程或 函數P 調用過程或函數Q,而Q 調用P,稱之為間接遞歸。對于“問題定義是遞歸的, 數據 結構是遞歸的, 問題解法是遞歸的”這3種情況, 都可以采用遞歸方法來處理。 遞歸算法的本質是把一個大型復雜的問題層層轉化為若干與原問題相似的規模較小的問題來 處理,當規模小到一定程度時,可以直接得出它的解,這樣通過遞推就可得到原來問題的解 。遞歸調用的次數必須是有限的,必須有遞歸結束的條件。遞歸算法的執行過程分為兩步 ,第一步是從目標出發追溯到源頭,稱為回溯。第二步是從源頭逐步回代達到目標,稱為遞 推。由于存在遞推,在回溯時,必須保留其返回的地址與參數,使程序能夠返回到調用處繼 續執行,這一步是系統通過設置棧來實現的,程序設計者無需對棧進行管理。 適宜用遞歸算法求解的問題的充要條件是:問題具有某種可借用的類同自身的子問題描述的 性質;某一有限步的子問題有直接的解存在。 遞歸過程或遞歸函數的參數值在遞歸過 程中必須是按規律變化的,且參數值的增/ 減方向應與遞歸終止條件相匹配,這樣才能控制 遞歸調用。 4 遞歸算法的實例 例1:用遞歸函數編程求n 的階乘n!。 階乘函數的遞歸定義如下: 這種定義方法是用階乘函數自身定義了階乘函數。由于n!和(n-1)!都是同一個問題的求解, 因此可將n!用遞歸函數來描述。程序代碼如下: long f(int n) { if ( n = = 0 ) return 1;//遞歸的終止條件及相應的操作 else return n * f (n-1);//遞歸調用 } 例2:中序遍歷二叉樹的遞歸算法。 void Inorder ( BTreeNode * BT ) { if ( BT != NULL ) { Inorder ( BT- > lchild); Visit (BT) ; Inorder ( BT- >rchild); } } 遞歸的執行依賴系統堆棧的支持,遞歸的執行過程主要分為兩步,回溯(逐層深入遞歸調用)和 遞推(層層向上遞歸返回),在回溯時需要做的工作有:①進行斷點保存,局部變量、形式參數 保存。②控制流程轉向遞歸調用的入口。 在本次遞歸調用結束后向上層調用返回時需要做的工作有:①保存本次調用的函數結果,恢復 調用函數時的局部變量和形式參數。②根據遞歸調用時的斷點地址將控制流程轉回到調用函 數中遞歸調用的下一行代碼處繼續執行。 例1:中求解4!的遞歸調用過程如下圖所示: 綜上所述,遞歸算法的執行過程是不斷地自調用,直到到達遞歸出口才結束自調用過程;到 達遞歸出口后,遞歸算法開始按最后調用的過程最先返回的次序返回;返回到最外層的調用 語句時遞歸算法執行過程結束。 遞歸算法在執行時,存在多次進棧和出棧,流程的跳轉和返回,甚至會出現多次重復計算, 從 而影響執行效率。還有一些高級程序設計語言沒有提供遞歸的機制和手段。因此,有些時候 將遞歸算法非遞歸化是有必要的。 非遞歸化最重要的是理解遞歸的執行過程。對于一般的遞歸算法,可以利用以下兩種方法對 其進行非遞歸化。 如果遞歸調用語句是函數的最后一條執行語句, 則稱這種遞歸調用為尾遞歸。當遞歸調用進 入內層時, 外層上與各形式參數對應的實際參數值和返回地址都會被編譯系統自動保存下來 , 以備返回時使用。對于尾遞歸, 調用返回時, 其后已沒有執行語句了。因而外層的實際參 數值不會再用到, 故沒有必要保留。此外, 由于遞歸調用語句是最后一條可執行語句, 返回 地址肯定在函數末尾, 故其返回地址也沒有必要保留下來。對于這種情況, 關鍵是從遞歸調 用出發, 從上而下遞歸到底, 找到遞歸的終止條件, 然后用循環實現遞歸算法的非遞歸化。 例3:求n的階乘n!的遞歸算法的非遞歸化。 例1中給出了求n的階乘n!的遞歸算法 從上而下遞歸: f(n)=n*f(n-1) f(n-1)=(n-1)*f(n-2) f(n-2)=(n-2)*f(n-3) : : f(2)=2*f(1) f(1)=1*f(0) f(0)=1 設最終結果用f表示,由遞歸的終止條件“n=0時,結果為1”知,f的初始值=1。由此,可從 下而上地用循環實現求f(i),其中i從1到任意正整數n,f隨著i的變化而變化,其非遞歸算法 如下: long f ( int n ) {int i; long f =1; for ( i=1; i<=n; i++) f =f * i; return f; } 類似的情形很多, 如求2 個正整數的最大公約數和求Fibonacci 數列等。 如果遞歸調用語句不是函數中的最后一個語句, 則稱該遞歸調用為非尾遞歸。對于非尾遞歸 調用中的入口地址, 計算機隱含地自動設置堆棧, 保留調用入口地址, 供遞歸返回使用。而 用非遞歸方法, 堆棧是人為設定, 顯現在程序中, 功能與遞歸算法相同。 由二叉樹的遞歸算法的執行過程知,在二叉樹非空時,首先訪問根的左子樹,再訪問根,最后訪 問根的右子樹;訪問根的左子樹時,先要訪問左子樹根的左子樹,再訪問左子樹的根,其次再訪 問左子樹根的右子樹……,如此遞歸下去,一直到樹的最左下結點被訪問(向左下搜索時,將當 前結點壓入系統棧中保存,以便向上回退時調出) ,然后訪問最左下結點的父結點,通過彈棧 獲得最左下結點的父結點,然后處理該父結點的右子樹,以此類推,循環直到整棵樹訪問完畢 。 根據上述對遞歸執行過程的分析,其對應的非遞歸算法為: void Inorder ( BTreeNode * BT ) {if ( ! BT) return ; Stack *S = Init-Stack() ; while (BT | | ! Empty-Stack(S) ) { while (BT) / / 當指針BT非空時入棧 {Push(S , BT) BT = BT - > lchild ; } Pop (S , BT) ; Visit (BT) ; BT = BT - > rchild ; } / / end while (BT| | Empty[JX*2]-[JX-*2]Stack(s) ) }/ / end InOrder () 遞歸算法具有代碼簡潔,思路清晰的優點,是設計算法的強有力工具。一般而言,遞歸程序的 執行效率低于非遞歸程序,但非遞歸算法往往難于編寫,容易出錯;理論上,遞歸算法都可以轉 化為非遞歸算法,但存在一些算法很難非遞歸化,如復雜的間接遞歸,因此,要根據問題需求及 軟件和硬件的環境等具體情形選擇遞歸還是非遞歸。 |
?
總結
- 上一篇: 外资对我国企业兼并控制情况的资料
- 下一篇: m3 pcb开孔 螺丝_螺丝过孔工艺孔底