Python 遞迴 (Recursion)
遞迴是指函數呼叫自己本身的程式設計技巧。遞迴可以將複雜的問題分解成較小的相同問題來解決。
基本概念
一個遞迴函數必須包含:
- 基本情況 (Base Case):停止遞迴的條件,避免無窮遞迴
- 遞迴情況 (Recursive Case):函數呼叫自己,但問題規模變小
def countdown(n):
# 基本情況
if n <= 0:
print("Done!")
return
# 遞迴情況
print(n)
countdown(n - 1)
countdown(5)
輸出:
5
4
3
2
1
Done!
經典範例
階乘 (Factorial)
n! = n × (n-1) × (n-2) × ... × 1
def factorial(n):
# 基本情況
if n == 0 or n == 1:
return 1
# 遞迴情況
return n * factorial(n - 1)
print(factorial(5)) # 120 (5 × 4 × 3 × 2 × 1)
print(factorial(0)) # 1
執行過程:
factorial(5)
= 5 * factorial(4)
= 5 * 4 * factorial(3)
= 5 * 4 * 3 * factorial(2)
= 5 * 4 * 3 * 2 * factorial(1)
= 5 * 4 * 3 * 2 * 1
= 120
費氏數列 (Fibonacci)
F(n) = F(n-1) + F(n-2),其中 F(0) = 0, F(1) = 1
def fibonacci(n):
# 基本情況
if n == 0:
return 0
if n == 1:
return 1
# 遞迴情況
return fibonacci(n - 1) + fibonacci(n - 2)
# 印出前 10 個費氏數
for i in range(10):
print(fibonacci(i), end=" ")
# 0 1 1 2 3 5 8 13 21 34
這個費氏數列實作效率很差,因為會重複計算很多次。實際使用時應該用迴圈或記憶化 (memoization) 優化。
最大公因數 (GCD)
使用輾轉相除法:
def gcd(a, b):
# 基本情況
if b == 0:
return a
# 遞迴情況
return gcd(b, a % b)
print(gcd(48, 18)) # 6
print(gcd(100, 25)) # 25
次方計算
def power(base, exp):
# 基本情況
if exp == 0:
return 1
# 遞迴情況
return base * power(base, exp - 1)
print(power(2, 10)) # 1024
print(power(3, 4)) # 81
處理資料結構
加總巢狀 List
def nested_sum(lst):
total = 0
for item in lst:
if isinstance(item, list):
total += nested_sum(item) # 遞迴處理巢狀 list
else:
total += item
return total
data = [1, [2, 3], [4, [5, 6]], 7]
print(nested_sum(data)) # 28
扁平化巢狀 List
def flatten(lst):
result = []
for item in lst:
if isinstance(item, list):
result.extend(flatten(item))
else:
result.append(item)
return result
data = [1, [2, 3], [4, [5, 6]], 7]
print(flatten(data)) # [1, 2, 3, 4, 5, 6, 7]
遍歷目錄結構
import os
def list_files(path, indent=0):
for item in os.listdir(path):
full_path = os.path.join(path, item)
print(" " * indent + item)
if os.path.isdir(full_path):
list_files(full_path, indent + 1)
# list_files("/path/to/directory")
遞迴的優化
記憶化 (Memoization)
儲存已計算的結果,避免重複計算:
def fibonacci_memo(n, memo={}):
if n in memo:
return memo[n]
if n == 0:
return 0
if n == 1:
return 1
memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
return memo[n]
print(fibonacci_memo(100)) # 很快就能算出來
使用 functools.lru_cache 裝飾器更簡潔:
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100))
尾遞迴 (Tail Recursion)
尾遞迴是指遞迴呼叫是函數的最後一個操作:
# 一般遞迴
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1) # 還要乘以 n
# 尾遞迴
def factorial_tail(n, acc=1):
if n == 0:
return acc
return factorial_tail(n - 1, n * acc) # 直接回傳遞迴結果
print(factorial_tail(5)) # 120
Python 沒有尾遞迴優化,所以尾遞迴在 Python 中並不會帶來效能提升。但這種寫法可以很容易轉換成迴圈。
遞迴深度限制
Python 預設的遞迴深度限制是 1000,超過會發生 RecursionError:
import sys
# 查看目前的限制
print(sys.getrecursionlimit()) # 1000
# 修改限制(謹慎使用)
sys.setrecursionlimit(2000)
如果需要很深的遞迴,通常應該改用迴圈實作,而不是增加遞迴限制。
遞迴 vs 迴圈
很多遞迴問題都可以用迴圈解決:
# 遞迴版本
def factorial_recursive(n):
if n == 0:
return 1
return n * factorial_recursive(n - 1)
# 迴圈版本
def factorial_iterative(n):
result = 1
for i in range(1, n + 1):
result *= i
return result
何時使用遞迴:
- 問題本身具有遞迴結構(如樹、圖)
- 程式碼更簡潔易讀
- 遞迴深度不會太深
何時使用迴圈:
- 效能要求高
- 遞迴深度可能很深
- 問題容易用迴圈表達