1. 程式人生 > >扒開衣服看遞迴--遞迴的本質

扒開衣服看遞迴--遞迴的本質

程式語言中,函式Func(Type a,……)直接或間接呼叫函式本身,則該函式稱為遞迴函式。
– 《百度百科》

我們一般運用遞迴演算法來解決以下的幾種問題:

  1. 資料的定義是按遞迴定義的。(Fibonacci函式,n的階乘)
  2. 問題解法按遞迴實現。(回溯)
  3. 資料的結構形式是按遞迴定義的。(二叉樹的遍歷,圖的搜尋)

可能到現在很多同學還沒弄明白什麼是遞迴,我們先來舉個簡單的例子。我的第一本計算機方面的書《C++程式語言設計》(譚浩強)中給出了一個這樣的例子:

五個人坐在一起,第五個人比第四個人大2歲,第四個比第三個大2歲,每個人都比後面一個人大2歲,第一個人是10歲,問第五個人是幾歲

我們腦海裡很快想出了他們之間的關係:

age(5) = age(4) + 2;
age(4) = age(3) + 2;
age(3) = age(2) + 2;
age(2) = age(1) + 2;
age(1) = 10;

我們可以推匯出一下的式子:

age(n) =  10   (n = 1)  //結束條件
age(n) = age(n - 1) +2 (n > 1) //推進條件。

每個遞迴函式總要具備兩個條件:

  1. 結束條件
  2. 推進條件

結束條件用來告訴函式什麼時候該停止,推進條件找出函式的一般條件,如本題的每個人比前面一個人大兩歲,來進行函式推進。

我們可以寫出以下的程式碼:

int age(int n)
{
   if(n == 1) c = 10;
   else return age(n - 1) + 2;
}

這段函式是怎麼運作的呢,就是按照我們上面給出的遞推關係,為了更好方便大家理解,畫出一個遞推的關係圖。

這裡寫圖片描述

那麼下面就來扒開衣服看本質,遞迴在記憶體中到底是怎麼一種運作方式呢。根據上面的圖,我們可以感覺到,遞迴的本質類似棧的性質,先進後出。

引用SpeedMe的一張圖來解釋

引用SpeedMe的一張圖來解釋。

函式的遞迴呼叫和普通函式呼叫是一樣的。當程式執行到某個函式時,將這個函式進行入棧操作,在入棧之前,通常需要完成三件事。

  1. 將所有的實參、返回地址等資訊傳遞給被調函式儲存。
  2. 為被調函式的區域性變數分配儲存區。
  3. 將控制轉移到北調函式入口。

當一個函式完成之後會進行出棧操作,出棧之前同樣要完成三件事。

  1. 儲存被調函式的計算結果。
  2. 釋放被調函式的資料區。
  3. 依照被調函式儲存的返回地址將控制轉移到呼叫函式。

上述操作必須通過棧來實現,即將整個程式的執行空間安排在一個棧中。每當執行一個函式時,就在棧頂分配空間,函式退出後,釋放這塊空間。所以當前執行的函式一定在棧頂。