引言
之前在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 | 0 | 1 |
5 | 1 | 1 |
4 | 1 | 2 |
3 | 2 | 3 |
2 | 3 | 5 |
1 | 5 | 8 |
ret1就是第n項的值,ret2就是第n+1項的值。
遞歸求斐波那契時,棧內存佔用以指數形式增長,而尾遞歸則是以線性方式增加,且無需重複計算值。