理解遞歸

在初學遞歸的時候, 看到一個遞歸實現, 咱們老是不免陷入不停的回溯驗證之中, 由於回溯就像反過來思考迭代, 這是咱們習慣的思惟方式, 可是實際上遞歸不須要這樣來驗證. 好比, 另一個常見的例子是階乘的計算. 階乘的定義: 「一個正整數的階乘(英語: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提到一種方法, 給我很大啓發, 該方法以下:函數

  1. 當n=0, 1的時候, 結果正確.
  2. 假設函數對於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)
}
相關文章
相關標籤/搜索