在初學遞歸的時候, 看到一個遞歸實現, 咱們老是不免陷入不停的回溯驗證之中, 由於回溯就像反過來思考迭代, 這是咱們習慣的思惟方式, 可是實際上遞歸不須要這樣來驗證. 好比, 另一個常見的例子是階乘的計算. 階乘的定義: 「一個正整數的階乘(英語:factorial)是全部小於或等於該數的正整數的積,而且0的階乘爲1。」 如下是Ruby的實現:算法
int factorial(n) if (n <= 1) return 1; else return n * factorial(n - 1);
咱們怎麼判斷這個階乘的遞歸計算是不是正確的呢? 先別說測試, 我說咱們讀代碼的時候怎麼判斷呢?
回溯的思考方式是這麼驗證的, 好比當n = 4時, 那麼factoria(4)
等於4 * factoria(3)
, 而factoria(3)
等於3 * factoria(2)
, factoria(2)
等於2 * factoria(1)
, 等於2 * 1
, 因此factoria(4)
等於4 * 3 * 2 * 1
. 這個結果正好等於階乘4的迭代定義.
用回溯的方式思考雖然能夠驗證當n = 某個較小數值是否正確, 可是其實無益於理解.
Paul Graham提到一種方法, 給我很大啓發, 該方法以下:函數
- 當n=0, 1的時候, 結果正確.
- 假設函數對於n是正確的, 函數對n+1結果也正確.
若是這兩點是成立的,咱們知道這個函數對於全部可能的n都是正確的。
這種方法很像數學概括法, 也是遞歸正確的思考方式, 事實上, 階乘的遞歸表達方式就是1!=1,n!=(n-1)!×n
. 當程序實現符合算法描述的時候, 程序天然對了, 假如還不對, 那是算法自己錯了…… 相對來講, n,n+1的狀況爲通用狀況, 雖然比較複雜, 可是還能理解, 最重要的, 也是最容易被新手忽略的問題在於第1點, 也就是基本用例(base case)要對. 好比, 上例中, 咱們去掉if n <= 1
的判斷後, 代碼會進入死循環, 永遠不會結束.測試
咱們常見的遞歸函數好比Fabpnpcci
函數能夠經過尾遞歸來進行優化,尾遞歸能進行優化的緣由是編譯器會自動識別尾遞歸而且爲其優化。 如今咱們來看一下兩個例子優化
int f(const int n){ if(n<1)return 0; if(n==1)return 1; else return f(n-1)*n; }
int f(const int n,const int res){ if(n==1||n==2)return 1; else return f(n-1,res+n) }