歡迎來到遞迴(Recursion)的世界!

在本章中,我們將探討編程中最優雅且強大的概念之一:遞迴(Recursion)。起初你可能會覺得這有點「燒腦」,但別擔心!我們會一步步拆解。當你讀完這些筆記後,你會發現遞迴其實就是一種看待問題的新角度——透過將大問題拆解成較小且相同的子問題來解決。

想像一下俄羅斯套娃(Matryoshka)。為了拿到最中間的小娃娃,你必須先打開一個大娃娃,接著是稍微小一點的,再小一點的,直到最後找到那個不能再打開的最小娃娃為止。


1. 什麼是遞迴?

在計算機科學中,當一個函式(function)在自己的程式碼中呼叫自己時,就會發生遞迴。這是一種解決問題的方法,其解決方案取決於相同問題在較小規模時的解法。

1.1 遞迴的基本特徵(課程大綱 1.4.1)

每個成功的遞迴函式都必須具備這三個基本特徵。若缺少它們,你的程式可能會當機或無限執行下去!

1. 基準情況(Base Case): 這是「停止條件」。它是問題最簡單的版本,可以直接得到答案,無需進一步遞迴。它能防止函式無限地自我呼叫。
2. 遞迴步驟(Recursive Step): 這是函式自我呼叫的部分。它處理的是原始問題的一個稍微縮小的版本。
3. 邁向基準情況(Progress Toward the Base Case): 每次函式呼叫自己時,它必須更接近基準情況(通常透過改變變數值來達成)。如果你沒有向基準情況推進,就會產生無限遞迴(Infinite Recursion)

快速複習盒:
記住 B.R.P. 口訣:
- Base Case (基準情況 - 停止)
- Recursive Step (遞迴步驟 - 重複)
- Progress (進度 - 逐步靠近終點)


2. 閱讀與編寫遞迴演算法(課程大綱 1.4.2)

讓我們看看經典範例:計算數字的階乘(Factorial)
\(n\) 的階乘(記作 \(n!\))是所有小於或等於 \(n\) 的正整數的乘積。
範例:\(4! = 4 \times 3 \times 2 \times 1 = 24\)

階乘的遞迴定義:

- 若 \(n = 0\),答案為 \(1\)(基準情況
- 若 \(n > 0\),答案為 \(n \times (n-1)!\)(遞迴步驟

Python 實作:

def factorial(n):
    if n == 0: # 基準情況
        return 1
    else: # 遞迴步驟
        return n * factorial(n - 1) # 進度:(n - 1) 讓我們更接近 0

你知道嗎? 如果你忘記寫基準情況,電腦會不斷地在記憶體中增加「層數」,直到耗盡記憶體空間。這就是所謂的堆疊溢位(Stack Overflow)


3. 追蹤遞迴:遞迴樹(課程大綱 1.5.5)

當遞迴函式被呼叫時,電腦不會立即完成第一次呼叫。它會「暫停」目前的呼叫並開始一個新的。我們可以使用遞迴樹(Recursion Tree)來視覺化這個過程。

讓我們追蹤 factorial(3)

1. 呼叫 factorial(3)。它需要 \(3 \times factorial(2)\)。(暫停!)
2. 呼叫 factorial(2)。它需要 \(2 \times factorial(1)\)。(暫停!)
3. 呼叫 factorial(1)。它需要 \(1 \times factorial(0)\)。(暫停!)
4. 到達基準情況: factorial(0) 回傳 \(1\)。
5. 現在我們開始「解開」(Unwind):
    - factorial(1) 變成 \(1 \times 1 = 1\)
    - factorial(2) 變成 \(2 \times 1 = 2\)
    - factorial(3) 變成 \(3 \times 2 = 6\)
最終答案:6

重點總結: 追蹤過程能幫你理解「解開」的過程(將值一層層傳回)是如何產生最終結果的。


4. 比較遞迴與迭代(課程大綱 1.4.3)

大多數可以用遞迴解決的問題,也可以用迭代(Iteration)(例如 forwhile 迴圈)來解決。它們的比較如下:

4.1 遞迴

優點:
- 程式碼通常更短、更簡潔、更優雅。
- 對於本質上是遞迴的問題(如樹的遍歷或「河內塔」問題)更容易求解。

缺點:
- 記憶體使用:較高。每次呼叫都會佔用呼叫堆疊(Call Stack)的空間。
- 速度:通常較慢,因為多個函式呼叫會產生額外的負擔(overhead)。

4.2 迭代

優點:
- 記憶體使用:較低。它重複使用相同的空間。
- 速度:通常較快,因為沒有函式呼叫的額外負擔。

缺點:
- 對於某些數學問題,程式碼可能會變得非常複雜且凌亂。

類比:
- 迭代就像是在跑道上跑圈。你處在同一個位置,只是重複同樣的動作。
- 遞迴就像是走旋轉樓梯。你雖然在做同樣的圓周運動,但每次都處在不同的高度(層級)。


5. 常見的錯誤避坑指南

1. 遺失基準情況:這是最常見的錯誤!一定要問自己:「這什麼時候會停止?」
2. 沒有進度:如果你在 factorial(n) 內部呼叫 factorial(n),\(n\) 的值永遠不會變,你就永遠無法到達基準情況。
3. 遞迴步驟邏輯錯誤:確保拆解問題的方式能導向正確的數學結果。


總結檢核清單

- [ ] 我知道三個基本特徵(基準情況、遞迴步驟、進度)嗎?
- [ ] 我能在一段程式碼中識別出基準情況嗎?
- [ ] 我能解釋為什麼遞迴比迴圈使用更多記憶體嗎?
- [ ] 我能畫出一棵簡單的樹來追蹤遞迴函式嗎?

如果一開始覺得很難,別擔心! 遞迴需要思維模式的轉換。試著用筆和紙追蹤一些小的範例;這是見證「魔法」發生的最好方法!