前言
前階段看博客,突然發現尾遞歸的概念,剛開始想,不就是遞歸嗎,后來仔細看了看不是那么回事。雖然沒有深入研究,但是通過一個經典的斐波那契數列實現可以看出尾遞歸和普通遞歸的區別。
什么是尾遞歸
如果一個函數中所有遞歸形式的調用都出現在函數的末尾,我們稱這個遞歸函數是尾遞歸的。當遞歸調用是整個函數體中最后執行的語句且它的返回值不屬於表達式的一部分時,這個遞歸調用就是尾遞歸。尾遞歸函數的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的代碼。(From 百度詞條)
當編譯器檢測到一個函數調用是尾遞歸的時候,它就覆蓋當前的活動記錄而不是在棧中去創建一個新的。編譯器可以做到這點,因為遞歸調用是當前活躍期內最后一條待執行的語句,於是當這個調用返回時棧幀中並沒有其他事情可做,因此也就沒有保存棧幀的必要了。通過覆蓋當前的棧幀而不是在其之上重新添加一個,這樣所使用的棧空間就大大縮減了,這使得實際的運行效率會變得更高(From百度詞條)
斐波那契數列
這個數列相信很多人都熟悉,面試也經常會問到。
1 1 2 3 5 8 13 21 34 55 89。。。。。。
用一個普通算法實現是醬紫的:
public int F(int n) { return n < 2 ? 1 : F(n - 1) + F(n - 2); }
由於是遞歸調用,每次調用F函數的時候,會導致F(n)重復計算。因為,每個值最終被拆解為 F(1)+F(0).
正如上圖,F(5)= F(1)+F(0)+F(1)+F(0)+F(1)+F(1)+F(0)+F(1) = 8
在看一下尾遞歸的實現:
public static int F(int n,int a1,int a2) { return n == 0 ? a1 : F(n - 1, a2, a1 + a2); }
在遞歸過程中,直接把計算結果作為參數傳入到遞歸方法中,也就是說,遞歸過程中不需要保存之前的計算值。其實這個方法也可以理解為一個方法的轉換。
int add(int x,int y)=>x+y; int add(s)=>s;
如上述代碼: add(1,2)=add(3);
我們在看尾遞歸的調用:F(5,1,1)=F(4,1,2)=F(3,2,3)=F(2,3,5)=F(1,5,8)=F(0,8,13)
所以,當我們調用F(5,1,1)的時候相當於變相的調用了F(0,8,13),正如上文中所說 :當編譯器檢測到一個函數調用是尾遞歸的時候,它就覆蓋當前的活動記錄而不是在棧中去創建一個新的。 因為后續的方法並不依賴於之前的方法。
總結
通過斐波那契數列能簡單的區分一下普通遞歸和尾遞歸的不同之處,當然這只是我淺層次的理解。有解釋不當之處還請來打我。