遞歸將問題分解為相同性質的子問題,通過函數調用自身求解。理解遞歸必須理解運行時棧。
高地址 ┌─────────────────┐ │ 調用者棧幀 │ ← 返回地址、實參、保存的寄存器 ├─────────────────┤ │ 被調用者棧幀 │ ← 形參、局部變量、返回地址 ├─────────────────┤ │ ... │ ├─────────────────┤ │ 遞歸第 n 層 │ ├─────────────────┤ │ 遞歸第 1 層 │ └─────────────────┘ 低地址
return_type func(params) {
if (base_case) return base_value; // 1. 終止條件
// 2. 縮小問題規模
// 3. 遞歸調用
return combine(func(smaller_params)); // 組合結果
}
void countdown(int n) {
if (n == 0) return; // 基例
printf("%d ", n); // 當前層工作
countdown(n - 1); // 遞歸步驟:問題規模 -1
}
// 輸出: 5 4 3 2 1
int sum(int n) {
if (n == 1) return 1; // 基例
return n + sum(n - 1); // 遞歸關係:sum(n) = n + sum(n-1)
}
// sum(100) = 5050
等價迭代:
int sum_iter(int n) {
int s = 0;
for (int i = 1; i <= n; i++) s += i;
return s;
}
尾調用:遞歸調用是函數最後一個動作,無需保留當前棧幀。編譯器可優化為跳轉 (O(1) 棧空間)。
// 非尾遞歸:返回後還需加 n
int sum(int n) { return n + sum(n-1); }
// 尾遞歸:引入累加器參數
int sum_tail(int n, int acc) {
if (n == 0) return acc;
return sum_tail(n - 1, acc + n); // 尾調用
}
// 調用: sum_tail(100, 0)
GCC/Clang -O2 可優化尾遞歸,MSVC 支持有限。
int binary_search(int *a, int l, int r, int target) {
if (l > r) return -1;
int m = l + (r - l) / 2;
if (a[m] == target) return m;
if (a[m] < target) return binary_search(a, m+1, r, target);
return binary_search(a, l, m-1, target);
}
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2); // 大量重複計算
}
// 優化:備忘錄/動態規劃/矩陣快速冪
void list_dir(const char *path, int depth) {
DIR *d = opendir(path);
struct dirent *e;
while ((e = readdir(d))) {
if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) continue;
printf("%*s%s\n", depth*2, "", e->d_name);
char sub[1024];
snprintf(sub, sizeof(sub), "%s/%s", path, e->d_name);
struct stat st;
if (stat(sub, &st) == 0 && S_ISDIR(st.st_mode))
list_dir(sub, depth+1);
}
closedir(d);
}
| 維度 | 遞歸 | 迭代 |
|---|---|---|
| 可讀性 | 數學定義直觀 (樹、分治) | 線性流程直觀 |
| 棧空間 | O(depth) 風險溢出 | O(1) |
| 性能 | 函數調用開銷 | 極致效率 |
| 適用場景 | 樹/圖遍歷、分治、回溯、解析器 | 線性處理、簡單累積 |