第一步,明確這個函數的功能是什麼,它要完成什麼樣的一件事。
而這個功能,是徹底由你本身來定義的。也就是說,咱們先無論函數裏面的代碼是什麼、怎麼寫,而首先要明白,你這個函數是要用來幹什麼的。數組
例如,求解任意一個數的階乘:
要作出這個題,
第一步,要明確即將要寫出的這個函數的功能爲:算n的階乘。函數
//算n的階乘(假設n不爲0) int f(int n) { }
遞歸:就是在函數實現的內部代碼中,調用這個函數自己。因此,咱們必需要找出遞歸的結束條件,否則的話,會一直調用本身,一直套娃,直到內存充滿。優化
第二步,咱們須要找出當參數爲什麼值時,遞歸結束,以後直接把結果返回。
(通常爲初始條件,而後從初始條件一步一步擴充到最終結果)code注意:這個時候咱們必須能根據這個參數的值,可以直接知道函數的結果是什麼。blog
讓咱們繼續完善上面那個階乘函數。
第二步,尋找遞歸出口:
當n=1時,咱們可以直接知道f(1)=1;
那麼遞歸出口就是n=1時函數返回1。
以下:遞歸
//算n的階乘(假設n不爲0) int f(int n) { if(n == 1) { return 1; } }
固然,當n=2時,咱們也是知道f(2)等於多少的,n=2也能夠做爲遞歸出口。遞歸出口可能並不惟一的。內存
第三步,咱們要從初始條件一步一步遞推到最終結果。(能夠類比數學概括法,多米諾骨牌)數學
這樣就能夠從n=1,一步一步推到n=2,n=3...變量
// 算n的階乘(假設n不爲0) int f(int n) { if(n = 1) { return n; } // 把f(n)的遞推關係寫進去 return f(n-1) * n; }
到這裏,遞歸三步走就完成了,那麼這個遞歸函數的功能咱們也就實現了。
可能初學的讀者會感受很奇妙,這就能算出階乘了?原理那麼,咱們來一步一步推一下。
f(1)=1
f(2)=f(1)*2=2
f(3)=f(2)*3=2*3=6
...
你看看是否是解決了,n都能遞推出來!
斐波那契數列的是這樣一個數列:一、一、二、三、五、八、1三、2一、34....,即第一項 f(1) = 1,第二項 f(2) = 1.....,第 n 項目爲 f(n) = f(n-1) + f(n-2)。求第 n 項的值是多少。
明確函數功能:f(n)爲求第n項的值
// 1.f(n)爲求第n項的值 int f(int n) { }
尋找遞歸出口:f(1)=1,f(2)=1
// 1.f(n)爲求第n項的值 int f(int n) { // 2.遞歸出口 if(n <= 2) { return 1; } }
找出遞推關係:f(n) = f(n-1)+f(n-2)
// 1.f(n)爲求第n項的值 int f(int n) { // 2.遞歸出口 if(n <= 2) { return 1; } // 3.遞推關係 return f(n-1) + f(n-2); }
一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
明確函數功能:f(n)爲青蛙跳上一個n級的臺階總共有多少種跳法
int f(int n) { }
尋找遞歸出口:f(0)=0,f(1)=1
int f(int n) { // 遞歸出口 if(n <= 1) { return n; } }
找出遞推關係:f(n) = f(n-1)+f(n-2)
int f(int n) { // 遞歸出口 if(n <= 2) { return 1; } // 遞推關係 return f(n-1) + f(n-2); }
上面說了那麼多,都是自底向上的
(我比較習慣自底向上,由於比較符合數學概括法,順着推)
例如,階乘能夠理解爲f(n)一步一步分解爲f(n-1)...直到f(1),一步步化小,這樣也是能夠的。
其實遞歸當中有不少子問題被重複計算。
對於斐波那契數列,f(n) = f(n-1)+f(n-2)。
遞歸調用的狀態圖以下:
其中,遞歸計算時f(6)、f(5)...都被重複了不少次,這是極大的浪費,當n越大,因重複計算浪費的就越多,因此咱們必需要進行優化。
代碼以下:
// 咱們實現假定 arr 數組已經初始化好的了。 int f(int n) { if(n <= 1) { return n; } //先判斷有沒計算過 if(arr[n] != -1) { //計算過,直接返回 return arr[n]; }else { // 沒有計算過,遞歸計算,而且把結果保存到 arr數組裏 arr[n] = f(n-1) + f(n-1); reutrn arr[n]; } }