在非負整數集上定義一個函數f,它知足f(0)=0,且f(x)=2f(x-1)+x^2.從這個定義能夠看出f(1)=1,f(2)=6,f(3)=21,f(4)=58。當一個函數用自身定義時就稱爲遞歸(recursive).即,一個函數直接或間接地調用自身,是爲直接或間接遞歸。C++是容許遞歸的。但必須記住,C++所作的僅僅是試圖遵循遞歸的思想。不是全部的數學遞歸函數都能有效的用C++遞歸模擬來實現。要點在於,遞歸函數f應該像非遞歸函數同樣只用幾行代碼就能表示出來。下圖給出了函數f的遞歸實現。面試
1 int f(int x) { 2 if (x == 0) 3 return 0; 4 else 5 return 2 * f(x - 1) + x * x; 6 }
第2行和第3行處理基準狀況(base case),即此時函數的值能夠直接算出來而不用遞歸。正如在沒有f(0)=0的前提下。聲稱f(x) = 2f(x - 1) + x^2.在數學上沒有意義同樣。C++的遞歸方法若無基準狀況也是毫無心義的。第5行執行的是遞歸調用。算法
編寫遞歸程序的時候,關鍵是要牢記遞歸的四條基本法則:編程
遞歸和循環數據結構
若是咱們要重複地屢次計算相同的問題,一般能夠選擇用遞歸或者循環兩種不一樣的方法。遞歸是在一個函數的內部調用這個函數自身。而循環這是經過設置計算的初始值及終止條件,在一個範圍內重複計算。好比求1+2+3+...+n,咱們能夠用遞歸或者循環兩種方式求出結果。對應的代碼以下:函數
int AddFrom1ToN_Recursive(int n) { return n <= 0 ? n + AddFrom1ToN_Recursive(n - 1); } int AddFrom1ToN_Iternative(int n) { int result = 0; for (int i = 0; i <= n; ++i) result += i; return result; }
一般遞歸的代碼比較簡潔。在上面的例子中,遞歸的代碼只有一個語句。而循環的則須要四個語句。在樹的前序、中序、後序遍歷算法的代碼中,遞歸的實現明顯要比循環簡單的多。性能
面試小提示:spa
一般基於遞歸的代碼要比基於循環實現的代碼要簡潔不少,更加容易實現。若是面試官沒有特殊要求,應聘者能夠優先採用遞歸的方法編程。.net
遞歸雖然有簡潔的優勢,但它同時也有顯著的缺點。設計
遞歸因爲是函數調用自身,而函數調用是有空間和時間的消耗的:每一次函數調用,都須要在內存棧中分配空間以保存參數、返回的地址及臨時變量,並且往棧裏壓入數據和彈出數據都須要時間。這就不難理解上述的例子中遞歸實現的效率不如循環。code
另外,遞歸中有可能不少計算都是重複的,從而對性能帶來很大的負面影響。遞歸的本質是把一個問題分解成兩個或多個小問題。若是多個小問題存在相互重疊的部分,那麼就存在重複的計算。
除了效率之外,遞歸還有可能引發更嚴重的問題:調用棧溢出。前面分析中提到須要爲每一次函數調用在內存棧中分配空間,而每一個進程的棧的容量是有限的。當遞歸調用的層級太多時,就會超出棧的容量,從而致使棧溢出。在上述的例子中,若是輸入的參數比較小,如10,它們都能返回結果55.但若是輸入的參數很大,好比5000,那麼遞歸代碼在運行的時間就會出錯,但運行循環的代碼能獲得正確的結果12502500.
相關資料:
1. http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=recursionPt1
2. http://www.nowamagic.net/librarys/veda/detail/2314
參考資料:
1. 《數據結構與算法分析C++描述》 Mark Allen Weiss
2. 《劍指offer》 何海濤