source: click here
[遞迴]-分- [遞推] 和 [回歸]
遞迴的概念及遞迴演算法的結構
1、所謂的遞迴,是指函式在執行過程中自己呼叫了自己或者說某種資料結構在定義時又引用了自身。這兩種情況都可理解為遞迴。比如:
void fun()
//fun
以上函式fun就是乙個遞迴函式。而針對於各種資料結構中的遞迴結構就更多了,如單鏈表,廣義表,樹。在這些遞迴結構中,具有乙個相同的特徵:其中的某個域的資料型別是其結點型別本身!
2、遞迴演算法的大致結構為:
a、遞迴出口
b、遞迴體
乙個遞迴演算法,當其問題求解的規模越來越小時必定有乙個遞迴出口,就是不再遞迴呼叫的語句。遞迴體則是每次遞迴時執行的語句序列。比如以下簡要描述的遞迴函式中:
f(n)=1 (當n=0時)
f(n)=n*f(n-1) (當n>0時)
這個遞迴函式,實際是求n的階乘。當n=0時,不再遞迴呼叫,而當其值置為1;當n>0時,就執行n*f(n-1),這是遞迴呼叫。從整體上理解遞迴演算法的大致結構有利於我們在設計遞迴演算法時,從總體上把握演算法的正確性。
二、棧與遞迴的關係:遞迴的執行
遞迴在實現過程中是借助於棧來實現的。高階語言的函式呼叫,每次呼叫,系統都要自動為該次呼叫分配一系列的棧空間用於存放此次呼叫的相關資訊:返回位址,區域性變數等。這些資訊被稱為工作記錄(或活動記錄)。而當函式呼叫完成時,就從棧空間內釋放這些單元,但是,在該函式沒有完成前,分配的這些單元將一直儲存著不被釋放。遞迴函式的實現,也是通過棧來完成的。在遞迴函式沒有到達遞迴出口前,都要不停地執行遞迴體,每執行一次,就要在工作棧中分配乙個工作記錄的空間給該「層」呼叫存放相關資料,只有當到達遞迴出口時,即不再執行函式呼叫時,才從當前層返回,並釋放棧中所占用的該「層」工作記錄空間。請大家注意,遞迴呼叫時,每次儲存在棧中的是區域性資料,即只在當前層有效的資料,到達下一層時上一層的資料對本層資料沒有任何影響,一切從當前呼叫時傳過來的實在引數重新開始。
由此可見,從嚴老師p版教材中,利用棧將遞迴向非遞迴轉化時所採用的方法,實質是用人工寫的語句完成了本該系統程式完成的功能,即:棧空間中工作記錄的儲存和釋放。大家在以後的作題時,可以參照以上的分析來理解遞迴函式的執行過程。實際上,現在的考試中,已經很少見到有學校要求運用棧與實現遞迴轉化為非遞迴來解題了,所以,大家能理解這個演算法更好,不能理解的也不用太擔心。我曾就此問題專門向嚴老師諮詢過,嚴老師說之所以在c版的教材中沒有講到這個演算法,也是考慮到了目前國內學校在這方面已經基本不作要求。但是,遞迴演算法的執行過程應該心中有數。
三、遞迴與遞推的關係
「遞迴演算法的執行過程分遞推與回歸兩個階段。在遞推階段,把較複雜的問題(規模為n)的求解推到比原問題簡單一些的問題(規模小於n)的求解。在回歸階段,當獲得最簡單的情況後,逐級返回,依次獲得稍複雜問題的解。」(摘自於「高程」教材)
「遞推法是利用問題本身所具有的一種遞推關係求問題解的一種方法。設要求問題規模為n的解,當n=1時,解或已知,或能非常方便地得到解。能採用遞推法構造演算法的問題有重要的遞推性質,即當得到問題規模為i-1的解後,由問題的遞推性質,能從已求得的規模為1,2,3、、、i-1的一系列解,構造出問題規模為i的解。直到最終得到問題規模為n的解。」
由此可見,遞推是遞迴的乙個階段,遞迴包含著遞推。當然,對於實際的演算法設計,知不知道這兩者之間的關係並不重要,重要的是我們能找出這其中的遞推規律和回歸時機。
四、適合於用遞迴實現的問題型別
必須具有兩個條件的問題型別才能用遞迴方法求得:
1、規模較大的乙個問題可以向下分解為若干個性質相同的規模較小的問題,而這些規模較小的問題仍然可以向下分解。
2、當規模分解到一定程度時,必須有乙個終止條件,不得無限分解。
由此可見適合於遞迴實現的問題型別有:
1、函式定義是遞迴的。如階乘,fib數列。
2、資料結構遞迴的相關演算法。如:樹結構。
3、解法是遞迴的
。如:漢諾塔問題。
五、遞迴演算法的設計
從遞迴演算法的結構來分析,進行遞迴演算法的設計時,無非要解決兩個問題:遞迴出口和遞迴體。即要確定何時到達遞迴出口,何時執行遞迴體,執行什麼樣的遞迴體。遞迴演算法演算法設計的關鍵是儲存每一層的區域性變數並運用這些區域性變數。由此,遞迴演算法的設計步驟可從以下三步來作:
1、分析問題,分解出小問題;
2、找出小問題與大問題之間的關係,確定遞迴出口;
3、用演算法語言寫出來。
六、遞迴演算法向非遞迴演算法的轉化方法
1、迭代法
如果乙個函式既有遞迴形式的定義,又有非遞迴的迭代形式的定義,則通常可以用迴圈來實現遞迴演算法的功能。
2、消除尾遞迴
尾遞迴,是一類特殊的遞迴演算法。它是指在此遞迴演算法中,當執行了遞迴呼叫後,遞迴呼叫語句後面再沒有其它可以執行的語句了,它即沒有用到外層的狀態,也沒有必要保留每次的返回位址,因為其後不再執行其它任何*作,所以可以考慮消除遞迴演算法。這種情況下,我們可以用迴圈結構設定一些工作單元來幫助消除尾遞迴,這些工作單元用於存放一層層的引數。
3、利用棧
當乙個遞迴演算法不利於用迭代法和消除尾遞迴法實現向非遞迴演算法的轉化時,可以考慮用棧來實現。實現的過程實際上就是用人工的方法模擬系統程式來儲存每層的引數,返回位址,以及對引數進行運算等。
一般情況下,對於遞迴演算法向非遞迴演算法的轉化問題,特別是結構定義時的遞迴演算法,我們通常先寫出遞迴演算法,然後再向非遞迴演算法轉化,而不是首先就嘗試寫出非遞迴演算法來。
如果運用列表來形容歸納法就是:
步進表示式:問題蛻變成子問題的表示式
結束條件:什麼時候可以不再是用步進表示式
直接求解表示式:在結束條件下能夠直接計算返回值的表示式
邏輯歸納項:適用於一切非適用於結束條件的子問題的處理,當然上面的步進表示式其實就是包含在這裡面了。
這樣其實就結束了,遞迴也就出來了。
遞迴演算法的一般形式:
void func( mode)
else }
遞迴的本質
遞迴簡單的來說遞迴就是乙個函式直接或間接地呼叫自身,是為直接或間接遞迴。一般來說,遞迴需要有邊界條件 遞迴前進段和遞迴返回段 其實就是進棧出棧的操作 當邊界條件不滿足時,遞迴前進 當邊界條件滿足時,遞迴返回。用遞迴需要注意以下兩點 1 遞迴就是在過程或函式裡呼叫自身。2 在使用遞迴策略時,必須有乙個...
遞迴討論(二)續
在討論二中,展示了鋼條分段的方法,只不過解題的方法採用的是自頂而下的策略。個人理解的自頂而下的策略 首先我們不知道乙個大問題的答案,但我們可以將問題規模縮小,外加上已知的一部分。進而變成求解這個小規模問題答案的過程,利用遞迴原理,層層向下,最終回到乙個最簡單的問題。這種處理問題的方式,難免會有重複求...
徹底理解遞迴,從遞迴的本質說起!
遍歷二叉樹,是學習樹這種資料結構首先要理解的一種基本操作。比較簡單地方式就是用遞迴去遍歷,鑑於遞迴這種呼叫方法有一定的特殊性,今天還是想來講講怎麼去理解遞迴遍歷。本文針對想理解遞迴的過程的朋友,因為本人在學到這一部分的時候也糾結了很久,其實只要理解了過程,那以後寫遞迴的 再也不用 心虛 了,因為那個...