算法與數據結構之美-遞歸

如何理解遞歸?

遞歸是一種應用非常廣泛的算法,許多數據結構的編碼實現都需要遞歸,例如DFS深度優先搜素、前中後序二叉樹遍歷等等。

在生活中,週末去看電影,但是不知道自己是在第幾排,這個時候你就會問前面一個人他是第幾排,前面的人要是不知道,就會再問他前面的人,直到第一排的人。然後再依次返回自己的排數,你就知道了答案。

遞歸,去的過程是傳遞,來的過程是迴歸,而電影院座位的問題,可以用遞推公式來表示:
f(n) = f(n-1)+1,f(1) = 1;

遞歸需要滿足的條件

  • 一個問題的解可以分爲幾個子問題的解
  • 該問題與分解後的子問題,除了數據規模不同,求解思路完全一樣
  • 存在遞歸終止條件

如何編寫遞歸代碼?

遞歸代碼的關鍵是遞推公式,記住這一點才能保證自己能夠寫出正確的遞歸代碼。

例子:
假設有n個臺階,一次可以上1個臺階或者2個臺階,請問走完這n個臺階有多少種走法?

思路:
思考一下,上臺階的走法其實可以根據第一步走1個臺階還是2個臺階分爲兩類,第一步是1個臺階的走法爲f(n-1),第一步是2個臺階的走法是f(n-2):
所以f(n)= f(n-1)+f(n-2),f(1) = 1,f(2)=2;代碼就不寫了,這裏有一點想說的是,我們在進行遞歸的時候,總是嘗試在腦海裏模擬整個遞歸過程,但其實分解一下,腦子就繞暈了。在遇到遞歸問題時,首先要能將問題分解成子問題,然後推導出遞推公式,就不用靠自己去分解遞歸的每個步驟。

遞歸警告

警惕堆棧溢出

函數會使用棧來保存臨時變量,每調用一個函數,都會將臨時變量封裝爲棧幀壓入內存棧,等函數執行完畢之後,出棧。系統棧或者虛擬機棧空間一般都不大,如果遞歸求解的數據規模很大,調用層次很深的話,一直壓入棧,就會有堆棧溢出的風險。

例如:我們將系統棧或者JVM堆棧大小設置爲1KB,在求解f(19999)時便會出現如下堆棧報錯:java.lang.StackOverflowError;

警惕重複計算

遞歸分解
從上圖可以看出,在遞歸分解過程,會有很多重複計算的問題;爲了避免重複計算,可以通過散列表來保存已經求解過的f(k)。除此之外,在遞歸過程中調用函數的數量過大時,就會增加空間和時間複雜度。

內容小結

遞歸是一種高效的編碼技巧,只需要滿足“三個條件”就可以通過遞歸來實現,需要正確的推導出遞推公式,找出終止條件,在翻譯成遞歸代碼。遞歸代碼雖然高效,但是也是有很多弊端的,需要警惕堆棧溢出、重複計算、函數調用超時等,所以需要考慮這些情況。

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