前言
盜夢空間想象大多數人都看過:電影講述的是主人公諾蘭進入希里安·墨菲夢境植入想法的行動。為了向希里安·墨菲夢植入理念,影片進入四層夢境,即所謂:“夢中的夢中 夢中人的夢中”。
有一對兔子,每隔三個月會產下一對小兔子,小免子每隔三個月,也會產生新的一對免子,問36個月后,共有多少對兔子。
諸如此類:其實就是“遞歸”,今天就一起進入“遞歸”的世界看看
正文
一、遞歸的定義
1.遞歸是一種應用廣泛的算法,既能運用到軟件開發中成為高效、簡潔的編碼技巧也能應用到生活中解決實踐遞歸問題,比如DFS深度優先搜索、前中后序二叉樹遍歷等,又比如計算不斷繁衍的后台個數等等;
2.程序調用自身的方式稱為遞歸調用,去調用的過程稱為遞,回來的過程稱為歸。
3.基本上,所有的遞歸問題都可以用遞推公式來表示,比如
f(n) = f(n-1) + 1;
f(n) = f(n-1) + f(n-2);
f(n)=n*f(n-1);
二、為什么使用遞歸?
1.遞歸在解決某些問題的時候使得我們思考的方式得以簡化,代碼也更加精煉,容易閱讀
2.遞歸在處理問題時要反復調用函數,這增大了它的空間和時間開銷,空間復雜度高、有堆棧溢出風險、存在重復計算、過多的函數調用會耗時較多等問題。
三、什么樣的問題可以用遞歸解決呢?
一個問題只要同時滿足以下3個條件,就可以用遞歸來解決:
1.問題的解可以分解為幾個子問題的解。何為子問題?就是數據規模更小的問題。
2.問題與子問題,除了數據規模不同,求解思路完全一樣
3.存在遞歸終止條件,不能無限循環。
四、如何實現遞歸
1.遞歸代碼編寫
寫遞歸代碼的關鍵就是將大問題分解為小問題,寫出遞推公式,找出終止條件,最后將遞推公式和終止條件翻譯成代碼。
舉一個栗子:
假如這里有n個台階,每一次你可以跨過一或二個台階,請問有幾種走法?
根據第一步的走法把走法分為兩類,第一步走一個台階或者走兩個台階,所以n個台階的走法就等於先走一階的走法加上先走兩個台階的走法,遞歸公式為:
f(n) = f(n-1)+f(n-2)
當只有一個台階時,我們就不需要遞歸了,所以終止條件為:
f(1)=1
但是只有它還不足夠,n=2時,f(2)=f(1)+f(0)還有f(0)=1,也就是第0階也要有一種走法,不和邏輯,所以終止條件還有一個:
f(2)=2
編寫為代碼為:
int f(int n) { if (n == 1) return 1; if (n == 2) return 2; return f(n-1) + f(n-2); }
2.遞歸代碼理解
對於遞歸代碼,若試圖想清楚整個遞和歸的過程,實際上是進入了一個思維誤區。
那該如何理解遞歸代碼呢?如果一個問題A可以分解為若干個子問題B、C、D,你可以假設子問題B、C、D已經解決。而且,你只需要思考問題A與子問題B、C、D兩層之間的關系即可,不需要一層層往下思考子問題與子子問題,子子問題與子子子問題之間的關系。屏蔽掉遞歸細節,這樣子理解起來就簡單多了。
因此,理解遞歸代碼,就把它抽象成一個遞推公式,不用想一層層的調用關系,不要試圖用人腦去分解遞歸的每個步驟。
五、遞歸常見問題及解決方案
1.警惕堆棧溢出:可以聲明一個全局變量來控制遞歸的深度,從而避免堆棧溢出。
代碼實現:
// 全局變量,表示遞歸的深度。
int depth = 0; int f(int n) { ++depth; if (depth > 1000) throw exception; if (n == 1) return 1; return f(n-1) + 1; }
2.警惕重復計算:通過某種數據結構來保存已經求解過的值,從而避免重復計算。
如用散列表來保存已存在的值,改寫上面的代碼如下:
public int f(int n) { if (n == 1) return 1; if (n == 2) return 2; // hasSolvedList 可以理解成一個 Map,key 是 n,value 是 f(n)
if (hasSolvedList.containsKey(n)) { return hasSovledList.get(n); } int ret = f(n-1) + f(n-2); hasSovledList.put(n, ret); return ret; }
(代碼來源於網絡)
六、如何將遞歸改寫為非遞歸代碼?
籠統的講,所有的遞歸代碼都可以改寫為迭代循環的非遞歸寫法。如何做?抽象出遞推公式、初始值和邊界條件,然后用迭代循環實現。
將上面的代碼實現如下:
int f(int n) { if (n == 1) return 1; if (n == 2) return 2; int ret = 0; int pre = 2; int prepre = 1; for (int i = 3; i <= n; ++i) { ret = pre + prepre; prepre = pre; pre = ret; } return ret; }
相關文章
以上內容為個人的學習筆記,僅作為學習交流之用。
歡迎大家關注公眾號,不定時干貨,只做有價值的輸出
作者:Dawnzhang
出處:https://www.cnblogs.com/clwydjgs/p/9810969.html
版權:本文版權歸作者
轉載:歡迎轉載,但未經作者同意,必須保留此段聲明;必須在文章中給出原文連接;否則必究法律責任