簡單來說,遞歸即是調用自己本身。所有遞歸都應該有至少一個基本條件,在滿足基本條件時不進行遞歸。
給出一個遞歸實例:
1 int fact(int N){ 2 if(N==1) 3 return 1; 4 else 5 return N*fact(N-1); 6 }
每一個遞歸方法的執行都分為前進和回退兩個階段,上例中計算5的階乘,前進階段得到的結果是:
(5*(4*(3*(2*(1)))))
回退階段則由內向外,依次計算括號中的值。
應用到程序中,分別對應壓棧和出棧。考慮到這種做法,每次調用都會壓棧出棧,效率很低。除此之外,每次遞歸創建新的棧在遞歸深度過深的時候,會引起棧溢出,也就是可以分給創建棧的內存不足的情況。尾遞歸的方法由此提出。
尾遞歸,簡單來說,就是將遞歸語句寫到最后一行且不參與任何計算。本質上,每次遞歸將接受上一次遞歸的傳參,並將本次計算結果傳給下一次遞歸,當遞歸到達終結條件的時候,其計算值即為返回值。這樣相比普通遞歸,省去了遞歸的回退過程,也即不用再回退到上一次遞歸作運算,自然就省去了很多創建棧、壓棧與出棧的工作,性能得到提升。將上例改寫為尾遞歸為:
1 int fact(int N){ 2 if(N==1) 3 return 1; 4 else 5 return fact(N-1,N); 6 } 7 8 int fact(int N, int M){ 9 if(N==1) 10 return M; 11 else 12 return fact(N-1, N*M); 13 }
這是有外及里的尾遞歸,當計算斐波那契數列的時候就要換一種思路,即由里及外。
斐波那契問題描述:每個兔子出生后的第三個月后,每個月都可以生下一對兔子,如果不考慮兔子死亡問題,第一個月有新出生的一對兔子,那第N個月有多少兔子?
其尾遞歸代碼如下:
1 int Fibonacci(int N, int first, int second, int begin){ 2 if(begin>=N) 5 return first; 6 else 7 return Fibonacci(N, second, first+second, begin+1); 8 }
但這要求編譯器能對尾遞歸進行優化,每次重用或者說覆蓋原來遞歸方法的棧,而不是新建棧。遺憾的是JAVA和python都不支持尾遞歸的優化,JAVA的尾遞歸代碼與普通遞歸無異。可能JVM是想在出現異常時更好地輸出堆棧信息的緣故。所以,JAVA中一般能用迭代就不用遞歸。