尾遞歸

遞歸

對於下面這個遞歸求階乘調用

private static long factorialRecursive(long n) {
    return n == 1 ? 1 : n * factorialRecursive(n - 1);
}

factorialRecursive調用在運行的時候每次執行factorialRecursive方法調用都會在調用棧上創建一個新的棧幀,用於保存每個方法調用的狀態也就是它需要需要進行n * factorialRecursive(n - 1)時候需要保存上一步(暫且叫做A函數)調用,在factorialRecursive(n - 1)(暫且叫做B)執行完成之前,在B完成之前,A會一直持有B的調用記錄,而在執行B的時候,又需要執行factorialRecursive(n - 1)(暫且叫做C),像ABC這樣的調用記錄也叫做”調用棧”.這個操作會一直到程序執行結束.這個過程比迭代執行單一機器級的分支指令大很多,當調用棧太長會造成StackOverflowError

遞歸調用棧過程如下圖
image

尾遞歸操作

爲了解決遞歸的這個問題,尾遞歸調用就出現了.

尾遞歸就是操作的最後一步[只]調用自身的操作,它不需要保存每次遞歸計算的中間值,編譯器就能自行決定複用某個棧幀進行計算

比如

function f(x) {
   if (x === 1) return 1;
   return f(x-1);
}

這是尾遞歸,它不要保存上一步計算結果

function f(x) {
   if (x === 1) return 1;
   return 1 + f(x-1);
}

上面這段代碼這不是尾遞歸,因爲下一步操作鏈仍然要參考上一步調用鏈

我們把求階乘的遞歸調用改成尾遞歸調用

private static long factorialRecursive(long result, long n) {
    if (n == 1)
        return result;
    return factorialRecursive(result * n, n - 1);
}

image

總的來說,尾遞歸比線性遞歸多一個參數,這個參數是上一次調用函數得到的結果;所以,關鍵點在於,尾遞歸每次調用都在收集結果,避免了線性遞歸不收集結果只能依次展開消耗內存的壞處。

但是目前java編譯器仍然不支持尾遞歸優化,上面寫到的java尾遞歸只是用於示範

發佈了126 篇原創文章 · 獲贊 46 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章