跳至主要内容

遞迴函式(Recursion Function)

· 閱讀時間約 4 分鐘
Ckai

函式可以調用函式本身

遞迴函式經典範例 ~ 階乘運算
package main

import "fmt"

func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}

func main() {
fmt.Println(factorial(5)) // 120
}

數學的階乘(例如:5! = 5 * 4 * 3 * 2 * 1)可透過上述代碼實現,如果調用 factorial(10) 回傳值為 3628800。

上述代碼 line 17 當中,factorial 函式在其函式主體內調用了 factorial 自身,這是遞迴函式的語法特徵。由於 factorial 函式主體的開頭設有判斷式,factorial 函式不會因為反覆調用自身而陷入無窮迴圈。

函式執行上下文(Function Execution Context)

上述代碼中,每當遞迴函式被調用時,堆疊內就會產生新的函式執行上下文,
直到到達終止條件 (n == 0),然後回傳計算結果。

Stack(堆疊)

堆疊是 FILO (First In Last Out) 的演算法。下列註解顯示上述代碼執行時,
堆疊內容納了五個 n * factorial(n-1)。

堆疊示意
/*
這裡是堆疊的出入口

return 1 * factorial(0) // 1 * 1 = 1
return 2 * factorial(1) // 2 * 1
return 3 * factorial(2) // 3 * 2 * 1
return 4 * factorial(3) // 4 * 3 * 2 * 1
return 5 * factorial(4) // 5 * 4 * 3 * 2 * 1

這裡是堆疊的底部
*/

上列註解中,line 5 以下的每個 factorial(n) 都會回傳上一行回傳的結果。
例如 line 7 是 4 * factorial(3),相當於 4 乘以 line 6 的 3 * 2 * 1。於是當堆疊的最後一個上下文要離開堆疊時,回傳的結果就是 5! = 120。

最大公因數、費氏數列、河內塔

看到以上標題的三個名詞,你會想到什麼共通點?
其實除了階乘運算,遞迴函式也可應用於最大公因數 (GCD)、費波納契數列 (Fibonacci Sequence)、河內塔 (Hanoi Tower)。這三個應用場景也很常用於解釋何為遞迴。

此外,LeetCode 考題「Merge Two Sorted Lists」也可用遞迴函式解題。

迭代與遞迴(Iterative & Recursive)

舉凡遞迴函式可以解決的問題,也可用迭代法解決。以下函式係以迭代法實現階乘運算。

package main

import "fmt"

func factorial(n int) int {
result := 1
for i := 1; i <= n; i++ {
result *= i
}
return result
}

func main() {
fmt.Println(factorial(5)) // 120
}

迭代與遞迴的優缺點

綜合考量各種情況時,通常迭代與遞迴的優缺點如下:

  • 代碼簡潔:迭代 < 遞迴(遞迴可讀性佳)
  • 時間複雜度:迭代 < 遞迴(遞迴較費時)
  • 空間複雜度:迭代 < 遞迴(堆疊佔用空間)

延伸閱讀

寫點科普:遞迴 (Recursive) 介紹與經典題型

PJCHENder:[演算法] 遞回函式(recursive function, recursion)