天天看點

斐波那契數列的遞歸與尾遞歸

引言

之前在lintcode上刷算法入門題,366題是求斐波那契數列,當時就想用遞歸應該很快就ac了,最後遞歸是沒寫錯,但是送出報時間超限了,也就引出了這篇文章——尾遞歸。

測試

先來看一下,正常的斐波那契遞歸寫法,假設第一項為0,第二項為1:

public int fibonacci(int n) {
    if(n == 1) {
	    return 0;
    }
    if(n == 2) {
	    return 1;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}
           

測試第40項的耗時結果為:

斐波那契數列的遞歸與尾遞歸

斐波那契的尾遞歸寫法:

public int lastFibonacci(int n, int ret1, int ret2) {
    if(n == 1) {
	    return ret1;
    }
    return lastFibonacci(n - 1, ret2, ret1 + ret2);
}
           

測試尾遞歸求第40項的耗時結果為:

斐波那契數列的遞歸與尾遞歸

可以看到當求到比較後面的項時,尾遞歸還是要快很多的。

分析總結

下面就來分析下尾遞歸到底快在了哪裡

正常遞歸

對于斐波那契數列的正常遞歸,有點類似于二叉樹的結構,我以f(6)為例,看下圖

斐波那契數列的遞歸與尾遞歸

可以看到遞歸計算第6項時,需要重複計算兩次f(4),三次f(3),以二叉樹的結構向下延伸,如測試的例子所示,當計算到第40項時,會有36項都需要重複計算,且越往下,重複次數越多,效率也很低下。

尾遞歸

尾遞歸快就快在它不需要重複計算某一項,利用了一種技巧就是每次遞歸調用時,它會把之前已經計算好的結果以參數的形式傳遞過去,同樣以f(6)為例

n ret1 ret2
6 1
5 1 1
4 1 2
3 2 3
2 3 5
1 5 8

ret1就是第n項的值,ret2就是第n+1項的值。

遞歸求斐波那契時,棧記憶體占用以指數形式增長,而尾遞歸則是以線性方式增加,且無需重複計算值。

繼續閱讀