尾遞歸優化到底是什么?


學數據結構時就知道這個概念,一直沒有研究過。

 

同樣一個求階乘的函數,首先是平時我們最熟悉的版本,也就是普通遞歸版本:

 

 

對於func(5)的遞歸調用如下:

 

 

 

 

然后是尾遞歸版本的:

 

 

 調用圖是這樣的:

 

 看起來,二者遞歸的棧都是五層嘛,有什么區別呢?

最大的區別是:對於第一種普通遞歸,每次函數的n*f(n-1)都要等f(n-1)調用返回后,再做乘法返回。也就是說,直到f(n-1)返回前,變量n的值都必須保存在棧上。

對於尾遞歸來說,函數加了一個參數來記錄之前函數計算的結果(有點類似動態規划有沒有?)。所以當前函數f(n,1)最終即將調用f(n-1,n)的時候,該棧(f(n,1)函數的棧)內的變量(比如n和cur_mul)都不再需要保存了。

所謂的尾遞歸優化不是說尾遞歸這種寫法是一種優化方法,而是說我們的代碼如果使用了尾遞歸那么編譯器會自動將代碼棧中的不再需要的空間優化掉,優化指的是編譯器對尾遞歸代碼的優化。說明白點,如果編譯器不做其他事情,我們的尾遞歸代碼和普通遞歸代碼的性能差距不大,都是要遞歸n層的。只不過一旦編譯器識別到尾遞歸代碼,就會將內部遞歸的函數直接開在之前的棧上(具體會更復雜,這樣只是簡單理解原理),這樣每層遞歸都是使用同一塊棧空間,防止了遞歸層數過高爆棧的可能。最終返回的時候,會直接把最深層的函數結果一步返回給最開始調用的函數。(實際上就是變成了循環!不再是遞歸了!)

 

貼兩個知乎回答:

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

什么是尾遞歸? - Frankie楊的回答 - 知乎 https://www.zhihu.com/question/20761771/answer/57214778

 
2.由於尾遞歸調用發生在函數末尾,它自己的棧幀中已經沒有需要被使用的東西了,也就可以讓下次遞歸調用直接覆蓋使用當前的棧幀。
這樣造成的結果就是尾遞歸在優化后,call / ret 其實被消除了(因為直接使用當前棧幀,不需要壓棧和出棧,函數體一般也能內聯),生成的機器碼和循環是一樣的。
什么是尾遞歸? - 孫竟的回答 - 知乎 https://www.zhihu.com/question/20761771/answer/19144609


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM