關於遞歸

遞歸是一個很有用的設計技術。在某些情況下,對於用其他方法很難解決的問題,使用遞歸就能給出一個自然、直接的簡單解法。
1、遞歸定義
遞歸定義由兩部分組成。第一部分稱作定位點或是基本例子,列出了構造集合中其他元素構造塊的基本元素。第二部分,給出構造除基本元素之外的或是已經創建好的新對象的條件。這個條件可以再三地被用來生成新對象。
遞歸定義有兩個目的:如前撰述的產生新元素,以及檢查它是否是集合的一個元素。在檢查時,先將一個問題簡化。如果簡化後仍然太複雜就繼續簡化,起到簡化後的問題指向定位點。
2、方法調用和遞歸實現
方法調用時應當保存什麼信息呢?道德是自動(局部)變量。如果一個包含局部變量x聲明的方法f1()調用f2()方法,而f2()局部地聲明瞭變量x,那麼系統必須區分這兩個變量x。如果f2()使用變量x,這表示它自己定義的x;如果f2()賦值給x,那麼f1()的變量x的值不應被改變。當f2()結束後,f1()可以使用在f2()調用前賦值給x。當f1()和f2()相同時,就是一個方法對它自身的遞歸調用。那麼系統是如何區分這兩個變量x的呢?
第一種方法(包括main()方法)的狀態都以所有自動變量的內容、方法的參數值以及表示調用程序重新開始處的返回地址爲特徵。包含所有這些信息的數據域定位在運行堆棧中,稱爲活動記錄或是棧結構。只要其中的一個方法在執行,屬於它的活動記錄就存在。這個記錄是該方法信息的一個私有池,一個存儲正確執行及返回調用位置的必要信息的倉庫。活動記錄的生存週期通常很短,因爲它們是在進入方法時動態分配並且在方法退出時動態回收的。只有main()的活動記錄比其他活動記錄的生存週期長些。
一個活動記錄通常包含以下信息:
該方法所有的參數值,首單元地址(如果傳遞的是數組或是引用變量的話),所有其他數據項的拷貝。
可以存在別處的局部變量,活動記錄只包含它們的描述符和指向它們存儲地址的指針。
存儲一個返回地址將控制權給調用程序,這個地址是緊跟在調用指令後的第一條指令的地址。
一個動態連接,即一個指向調用程序活動記錄的指針。
一個未被聲明爲void類型的方法的返回值。因爲每個調用的活動記錄的大小都是不同的,所以返回值就放在調用程序的活動記錄的前面。
如前撰述,如果一個方法被main()或另一個方法調用,那麼它的活動記錄就創建在運行堆棧中。運行堆棧總是反映方法的當前狀態。例如,假設main()調用方法f1(),f1()調用f2(),而f2()又調用f3()。如果f3()正在執行中,那麼運行堆棧的狀態如圖所示。根據堆棧的性質,如果彈出了f3()的活動記錄,只要將指針移到f3()的返回值下面就可以,那麼f2()又恢復運行,並可以自由地訪問重新被激活運行所必需的信息私有池。另一方面,如果f3()剛巧又調用了另一個該方法f4(),那麼運行堆棧將上移棧頂指針,因爲f4()的活動記錄被創建在堆棧中並且f3()會被掛起。

無論何時調用一個方法都會創建一個活動記錄,這使得系統可以正確處理遞歸。遞歸是調用一個和調用者同名的方法,所以一個遞歸調用並不是一個方法調用其自身,而是方法的一個實例調用相同方法的另一個實例。在計算機內部,這些調用是用不同的活動記錄表示並由系統區分的。
小結:
遞歸通常比它的迭代形式效率低。但是打個比方,如果一個遞歸程序執行100ms,而它的迭代公需要10ms,那麼儘管後者比前者快10倍,這種差別還是很難分辨的。如果要比清晰性、易讀性和代碼的簡潔性,那麼這兩個版本執行時間上的差別就可以忽略。遞歸解決方案通常比迭代簡單,而且和源算法的邏輯一致性強。階乘和冪運算就是這樣的例子。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章