跳去內容

遞歸 (電腦科學)

出自維基百科,自由嘅百科全書

遞歸粵拼dai6 gwai1英文recursion)喺程式編寫上,如果係講緊子程式嘅定義,狹義上係指子程式嘅定義會直接用到[註 1]自己,廣義上就指子程式會直接或者間接用到自己[1]:41;如果講子程式嘅執行,指嘅係指子程式會直接或者間接啟動[註 2]自己[1]:121。遞歸可以用來將一條問題分做條問題「細啲嘅版本」噉嚟解[2]

狹義上子程式嘅遞歸,原則上同邏輯上講嗰種遞歸上係同一樣嘢,都係 「用自己定義自己」,但係子程式嘅啟動牽涉有限資源同副作用等等;廣義嘅遞歸,或者執行嘅時候講嘅遞歸,都可以視為狹義嘅遞歸喺特定語境之下嘅引伸義。

喺程式語言嘅理論上,遞歸有兩種特別情形:如果喺實際執行嘅時候,一個子程式嘅啟動(activation)最多只會再啟動自己一次,呢種叫 linear recursion(線性遞歸)[1]:42。如果一個子程式一係唔啟動自己,一係就喺輸出(return)嘅時候先啟動自己,呢種叫尾遞歸(tail recursion)[1]:43;喺翻譯嘅時候,尾遞歸係可以有方法令佢實際上變成唔涉及遞歸嘅其他嘅流程控制[1]:143

概論

[編輯]
睇埋:控制流程

基本諗頭

[編輯]
内文:子程式遞歸

遞歸最重要嘅特徵,係指一個子程式會「用到自己」或者「𠹭call自己」。呢種做法可以當係分治演算法[e 1]嘅一類,將一個大嘅問題「揼散」做若干個細嘅問題[3]。舉例說明,想像依家要教電腦階乘嘅數,可以想像以下嘅虛擬碼[2][4]

呢個子程式叫 factorial(x)
如果 x == 0:
Output 係 1
否則:
Output 係 x 乘以 factorial(x-1)

——factorial 呢個子程式,會「用返自己」。假如 input x 數值係 5,行呢個程式就會好似噉:

factorial(x) 行嗰陣嘅情況
  1. 個程式行 factorial(x-1),呢個 factorial 以 4 做 input;
  2. 個程式行 factorial(x-1),呢個 factorial 以 3 做 input;
  3. 個程式行 factorial(x-1),呢個 factorial 以 2 做 input;
  4. 個程式行 factorial(x-1),呢個 factorial 以 1 做 input;
  5. 個程式行 factorial(x-1),呢個 factorial 以 0 做 input;
  6. 因為 x 數值去到 0,個子程式出 1;
  7. return x * factorial(x-1)factorial(0) 出 1,1 * 1 出 1;
  8. return x * factorial(x-1)factorial(1) 出 1,2 * 1 出 2;
  9. return x * factorial(x-1)factorial(2) 出 2,3 * 2 出 6;
  10. return x * factorial(x-1)factorial(3) 出 6,4 * 6 出 24;
  11. return x * factorial(x-1)factorial(4) 出 24,5 * 24 出 120;
  12. 最後成個程式出 120 做答案。

如是者,個程式就計到階乘

兩大部份

[編輯]
睇埋:無限迴圈

一個用咗遞歸嘅子程式,有兩大組成部份:基本個案同遞歸個案:

  • 基本個案[e 2]會「即刻畀 output」,唔會引致任何進一步嘅遞歸,電腦一𠹭佢,就會得到 output。好似係計階乘嗰個例子噉,佢個基本個案就係 如果 x == 0:output 係 1 嗰一截。用親遞歸嘅子程式都梗要有個基本個案,唔係嘅話個子程式就會進入無限迴圈
  • 遞歸個案[e 3]會有進一步遞歸,但係每一次遞歸個問題都會「細咗」或者「簡單咗」,而且會愈嚟愈接近基本個案。喺計階乘嘅例子裡便,佢個遞歸個案係 否則:output 係 x 乘以 factorial(x-1) [註 3]嗰截——呢段嘢每行一次,個問題都會愈嚟愈接近基本個案。

有啲人會將上述過程想像成「一個堆堆裝住未解嘅遞歸個案,遞歸個案一個個噉堆,堆到去到基本個案,再一個個噉解咗佢」[5]

遞歸種類

[編輯]

「遞歸嘅威力在於佢能夠用有限嘅陳述式,定義無限咁多件物件。同一道理,有限嘅遞歸程式可以描述數量無限嘅運算,而且就算個程式冇講明要重複都得。」

——尼克勞斯·維爾特[6]

單一定多重

[編輯]
費氏數列嘅圖像化:圖中每個正方形嘅邊長係費氏數列其中一個數。

遞歸可以分做單一多重兩種。呢個分類係講緊個子程式會𠹭call佢自己𠹭幾多次,單一遞歸比較簡單,個子程式嘅定義只會𠹭自己一次,而多重遞歸就比較複雜,個子程式嘅定義會𠹭自己超過一次。

例如費氏數列[e 4]就可以用多重遞歸嚟計[7]。費氏數列係一個好出名嘅數列,頭兩個數係 01,跟住落嚟嘅數就係之前兩個數加埋嘅和,即係:

,而且

想像以下呢段演算法嘅虛擬碼,用家是但入一個正整數 n,段演算法會計出費氏數列入便第 n 個數係乜[7][8]

定義子程式 fibGen(self, n):
如果 n <= 1:
畀 n 做答案
否則
畀 fibGen(n-1) + fibGen(n-2)

好多編程工作者都主張,多重遞歸呢家嘢可免則免。一個程式用咗多重遞歸,好易出現運算複雜度過高嘅問題。例如睇返上面嗰段計費氏數列嘅演算法噉,個子程式每行一次,都會𠹭自己兩次,然後嗰兩個𠹭就會各自產生多兩個𠹭變成四個𠹭——兩重遞歸嘅運算複雜度會傾向同 成正比。而如果段演算法有三重遞歸,個子程式每行一次,就會𠹭自己三次,而三個𠹭都會各自產生多三個𠹭變成九個𠹭——三重遞歸嘅運算複雜度會傾向同 成正比[註 4]

直接定間接

[編輯]
内文:相互遞歸

尾遞歸

[編輯]
内文:尾遞歸

理論分析

[編輯]

出名應用

[編輯]
睇埋:樹狀數據碎形

喺編程上,遞歸可以用嚟解決一啲可以揼散做一大柞細問題嘅問題,而且當中每一個細問題都屬同一個類型。

對比疊代

[編輯]
睇埋:疊代

常見錯誤

[編輯]

睇埋

[編輯]

文獻

[編輯]
  • Dijkstra, Edsger W. (1960). "Recursive Programming". Numerische Mathematik. 2 (1): 312–318. doi:10.1007/BF01386232.

參考

[編輯]

註釋:

  1. 理論上跟數學講 apply,編程通常講 call
  2. 理論上講 activate,編程通常叫 call
  3. 嚴格嚟講,呢度個否則係不必要嘅。
  4. 如果要用圖像化嘅方式諗,可以用樹狀圖嚟思考。

篇文用咗嘅行話詞彙,英文名如下:

  1. divide-and-conquer
  2. base case
  3. recursive case
  4. Fibonacci sequence

篇文引用咗以下呢啲文獻網頁

  1. 1.0 1.1 1.2 1.3 1.4 Sethi, Ravi (1990) [1989]. Programming Languages: Concepts and Constructs. Reading, MA: Addison-Wesley. ISBN 0-201-10365-6.
  2. 2.0 2.1 Jaimini, Vaibhav. "Recursion and Backtracking".
  3. Brassard, G., and Bratley, P. Fundamental of Algorithmics, Prentice-Hall, 1996.
  4. Reading 14: Recursion麻省理工學院
  5. How Recursion Works — Explained with Flowcharts and a Video. freeCodeCamp.
  6. Wirth, Niklaus (1976). Algorithms + Data Structures = Programs. Prentice-Hall. p. 126. "The power of recursion evidently lies in the possibility of defining an infinite set of objects by a finite statement. In the same manner, an infinite number of computations can be described by a finite recursive program, even if this program contains no explicit repetitions."
  7. 7.0 7.1 Understanding Multiple Recursion Calls: Unraveling the Fibonacci Problem. Medium.
  8. Multiple recursion.

[編輯]