迴圈與遞迴是程式設計的兩大控制流基石。本文透過三個經典範例——累加求和、陣列搜尋、鏈表遍歷——展示如何將迴圈改寫為遞迴,並分析兩者差異。
1 + 2 + ... + mint sum_loop(int m)
{
if (m < 0) return -1;
int total = 0;
for (int i = 0; i <= m; ++i) total += i;
return total;
}
int sum_rec(int m)
{
if (m == 0) return 0;
return m + sum_rec(m - 1);
}
| 維度 | 迴圈 | 遞迴 |
|---|---|---|
| 執行方向 | 0 → m(正向累加) | m → 0(反向分解) |
| 堆疊使用 | O(1) | O(m) 呼叫堆疊 |
| 程式碼長度 | 較長 | 極短 |
| 效能 | 極快,無函數呼叫開銷 | 較慢,函數呼叫/返回開銷 |
int find_loop(const int a[], int n, int val)
{
for (int i = 0; i < n; ++i)
if (a[i] == val) return i;
return -1;
}
int find_rec_helper(const int a[], int n, int val, int idx)
{
if (idx == n) return -1;
if (a[idx] == val) return idx;
return find_rec_helper(a, n, val, idx + 1);
}
int find_rec(const int a[], int n, int val)
{
if (a == NULL || n == 0) return -1;
return find_rec_helper(a, n, val, 0);
}
此為尾端遞迴,編譯器最佳化可自動轉為迴圈,消除堆疊增長。
typedef struct Node { int data; struct Node *next; } Node;
/* 迴圈 */
void print_loop(const Node *head)
{
for (const Node *p = head; p; p = p->next)
printf("%d\n", p->data);
}
/* 遞迴 */
void print_rec(const Node *head)
{
if (!head) return;
printf("%d\n", head->data);
print_rec(head->next);
}
遞迴版極度簡潔,但長鏈表恐爆堆疊。