尾遞迴以及優化

SJLin96發表於2018-08-21

參考文章:

尾遞迴與Continuation

淺談尾遞迴的優化方式(這篇和上面那篇都出自知乎三大程式設計大佬之一:趙劼)

遞迴與尾遞迴總結

什麼是尾遞迴?

 

一.  先看一個來自知乎的通俗例子

  • 尾遞迴:
    function story() {
    從前有座山,山上有座廟,廟裡有個老和尚,一天老和尚對小和尚講故事:story()
    // 尾遞迴,進入下一個函式不再需要上一個函式的環境了,得出結果以後直接返回
    }
  • 非尾遞迴:
    function story() {
    從前有座山,山上有座廟,廟裡有個老和尚,一天老和尚對小和尚講故事:story(),小和尚聽了,找了塊豆腐撞死了
    // 非尾遞迴,下一個函式結束以後此函式還有後續,所以必須儲存本身的環境以供處理返回值。
    }

 

二. 遞迴和尾遞迴

  • 我們都知道,在遞迴操作中,可能要通過棧來儲存一些當前函式的資訊。如果一個函式遞迴非常多次,就很可能引發棧溢位。
  • 尾遞迴是:如果一個函式中所有遞迴形式的呼叫都出現在函式的末尾,我們稱這個遞迴函式是尾遞迴的。當遞迴呼叫是整個函式體中最後執行的語句且它的返回值不屬於表示式的一部分時,這個遞迴呼叫就是尾遞迴。尾遞迴函式的特點是在迴歸過程中不用做任何操作(也就是說可以不用儲存函式的資訊),這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的程式碼。

 

  • 尾遞迴就是從最後開始計算,每遞迴一次就算出相應的結果。也就是說,函式呼叫出現在呼叫者函式的尾部。因為是尾部,所以根本沒有必要去儲存任何區域性變數。直接讓被呼叫的函式返回時越過呼叫者,返回到呼叫者的呼叫者去。
  • 尾遞迴就是把當前的運算結果(或路徑)放在引數裡傳給下層函式,深層函式所面對的不是越來越簡單的問題,而是越來越複雜的問題,因為引數裡帶有前面若干步的運算路徑。

 

  • 舉個例子:
//直接遞迴求連結串列的長度 
int GetLengthRecursive(linklist head)
{
     if(head->next == NULL)
        return 0;
     return (GetLengthRecursive(head->next) + 1);
}

//採用尾遞迴求連結串列的長度,藉助變數acc儲存當前連結串列的長度,不斷的累加 
int GetLengthTailRecursive(linklist head,int *acc)
{
     if(head->next == NULL)
       return *acc;
     *acc = *acc+1;
     return GetLengthTailRecursive(head->next,acc);
}

 

相關文章