系列: Algorithms
cpp
519 行
· 更新于 2026-03-28
recursion_examples.cpp
Algorithms/recursion_examples.cpp
/*
* ============================================================================
* 遞迴 (Recursion) 完整範例 — 遞迴版 vs 非遞迴(迭代)版
* File: recursion_examples.cpp
*
* 每個範例都同時提供遞迴版和迭代版,並說明轉換的方法。
*
* 範例列表:
* 1. 階乘 (Factorial) — 直接迴圈
* 2. 階乘(尾遞迴版) — 累加器參數
* 3. Fibonacci(天真遞迴) — O(2^N) vs 迭代 O(N)
* 4. Fibonacci(Memoization) — 記憶化 O(N)
* 5. 二元搜尋 (Binary Search) — 尾遞迴 → while
* 6. GCD(歐幾里得) — 尾遞迴 → while
* 7. 河內塔 (Tower of Hanoi) — 手動 Stack
* 8. 二元樹 Inorder 遍歷 — 手動 Stack
* 9. 反轉字串 — 雙指標
* 10. Quick Sort — Stack 存區間
*
* Compile: g++ -std=c++11 -Wall -o recursion_examples recursion_examples.cpp
* Run: ./recursion_examples
* ============================================================================
*/
#include <iostream>
#include <vector>
#include <stack>
#include <string>
#include <algorithm>
#include <utility>
using namespace std;
/* ============================================================================
* 1. 階乘 (Factorial)
*
* 定義: n! = n × (n-1) × ... × 1, 0! = 1
*
* 轉換方法:【直接用 for 迴圈取代】
* - 遞迴從 n 遞減到 0 → 迴圈從 1 遞增到 n
* - base case 的值 (1) → 迴圈的初始值
* ============================================================================ */
// 遞迴版 — 每次呼叫 n 減 1,到 n=0 時回傳 1
// 時間 O(N),空間 O(N)(call stack)
long long factorialRec(int n) {
// Base case: 0! = 1
if (n <= 0) return 1;
// Recursive case: n! = n × (n-1)!
return (long long)n * factorialRec(n - 1);
}
// 迭代版 — 用 for 迴圈累乘
// 時間 O(N),空間 O(1)
long long factorialIter(int n) {
long long result = 1;
// 從 1 乘到 n(方向和遞迴相反)
for (int i = 1; i <= n; ++i) {
result *= i;
}
return result;
}
/* ============================================================================
* 2. 階乘(尾遞迴版)
*
* 轉換方法:【累加器 (accumulator) 參數】
* - 把「回來後要做的乘法」提前到參數裡
* - 遞迴呼叫是函式的最後一步 → 尾遞迴
* - 編譯器可優化為不使用額外 Stack 空間
* ============================================================================ */
// 尾遞迴版 — acc 累積結果,遞迴呼叫是最後一步
// 時間 O(N),空間 O(1)(若編譯器做 TCO)
long long factorialTail(int n, long long acc = 1) {
// Base case: 累積完畢
if (n <= 0) return acc;
// Tail call: 把 n * acc 往下傳,回來後不再做任何運算
return factorialTail(n - 1, (long long)n * acc);
}
/* ============================================================================
* 3. Fibonacci
*
* 定義: F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
*
* 天真遞迴: O(2^N) — 大量重複計算,極慢!
* 迭代: O(N) — 只保留前兩項
*
* 轉換方法:【Bottom-up 迴圈(動態規劃)】
* - 遞迴是 top-down (從 n 往 0)
* - 迭代是 bottom-up (從 0 往 n)
* ============================================================================ */
// 遞迴版 — O(2^N),n > 40 就非常慢
long long fibRec(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
// 兩個遞迴呼叫 → 指數級爆炸
return fibRec(n - 1) + fibRec(n - 2);
}
// 迭代版 — O(N) 時間,O(1) 空間
long long fibIter(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
long long prev = 0, curr = 1;
for (int i = 2; i <= n; ++i) {
long long next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
/* ============================================================================
* 4. Fibonacci(Memoization 記憶化)
*
* 轉換方法:【加一個陣列/map 記錄已算過的結果】
* - 保持遞迴的寫法,但避免重複計算
* - 時間從 O(2^N) 降到 O(N)
* - 空間 O(N)(memo 陣列)
* ============================================================================ */
static vector<long long> memo;
long long fibMemo(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
// 如果已經算過,直接回傳
if (memo[n] != -1) return memo[n];
// 否則算一次,存起來
memo[n] = fibMemo(n - 1) + fibMemo(n - 2);
return memo[n];
}
/* ============================================================================
* 5. 二元搜尋 (Binary Search)
*
* 在已排序陣列中尋找 target。
*
* 轉換方法:【尾遞迴 → while 迴圈】
* - 遞迴呼叫是最後一步(尾遞迴)
* - 用 while 迴圈直接更新 low/high
* ============================================================================ */
// 遞迴版
int bsRec(const vector<int>& a, int low, int high, int target) {
// Base case: 沒找到
if (low > high) return -1;
int mid = low + (high - low) / 2;
if (a[mid] == target) return mid;
if (target < a[mid])
return bsRec(a, low, mid - 1, target); // 搜尋左半
else
return bsRec(a, mid + 1, high, target); // 搜尋右半
}
// 迭代版 — 把遞迴的「更新參數」改為「更新變數」
int bsIter(const vector<int>& a, int target) {
int low = 0, high = (int)a.size() - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == target) return mid;
if (target < a[mid])
high = mid - 1; // 對應 bsRec(a, low, mid-1, target)
else
low = mid + 1; // 對應 bsRec(a, mid+1, high, target)
}
return -1;
}
/* ============================================================================
* 6. GCD — 歐幾里得演算法
*
* gcd(a, b) = gcd(b, a % b), gcd(a, 0) = a
*
* 轉換方法:【尾遞迴 → while 迴圈】
* ============================================================================ */
// 遞迴版(天然的尾遞迴)
long long gcdRec(long long a, long long b) {
if (b == 0) return a;
return gcdRec(b, a % b);
}
// 迭代版
long long gcdIter(long long a, long long b) {
while (b != 0) {
long long temp = b;
b = a % b;
a = temp;
}
return a;
}
/* ============================================================================
* 7. 河內塔 (Tower of Hanoi)
*
* 轉換方法:【手動模擬 Stack】
* - 把每次遞迴呼叫的參數 (n, from, to, aux) 存入 Stack
* - 用 while 迴圈 pop + 處理
* ============================================================================ */
// 遞迴版
void hanoiRec(int n, char from, char to, char aux) {
if (n == 1) {
cout << " Move disk 1 from " << from << " to " << to << endl;
return;
}
hanoiRec(n - 1, from, aux, to); // 上面 n-1 個搬到 aux
cout << " Move disk " << n << " from " << from << " to " << to << endl;
hanoiRec(n - 1, aux, to, from); // n-1 個從 aux 搬到 to
}
// 非遞迴版 — 手動 Stack 模擬
// Stack 中的每個 frame 記錄:(n, from, to, aux, phase)
// phase 0: 尚未處理第一個遞迴呼叫
// phase 1: 第一個遞迴完成,該移動盤子了
// phase 2: 該處理第二個遞迴呼叫了
struct HanoiFrame {
int n;
char from, to, aux;
int phase;
};
void hanoiIter(int n, char from, char to, char aux) {
stack<HanoiFrame> s;
s.push({n, from, to, aux, 0});
while (!s.empty()) {
HanoiFrame& f = s.top();
if (f.n == 1) {
// Base case: 直接移動
cout << " Move disk 1 from " << f.from << " to " << f.to << endl;
s.pop();
continue;
}
if (f.phase == 0) {
// 第一步:把上面 n-1 個從 from 搬到 aux(使用 to 作輔助)
f.phase = 1;
s.push({f.n - 1, f.from, f.aux, f.to, 0});
} else if (f.phase == 1) {
// 第二步:移動最大的盤子
cout << " Move disk " << f.n << " from " << f.from << " to " << f.to << endl;
f.phase = 2;
// 第三步:把 n-1 個從 aux 搬到 to(使用 from 作輔助)
s.push({f.n - 1, f.aux, f.to, f.from, 0});
} else {
// phase == 2: 所有子任務完成
s.pop();
}
}
}
/* ============================================================================
* 8. 二元樹 Inorder 遍歷
*
* 轉換方法:【手動 Stack 模擬】
* - 先沿左邊一路往下 push
* - pop 時訪問節點
* - 然後轉向右子樹
* ============================================================================ */
struct TreeNode {
int data;
TreeNode* left;
TreeNode* right;
TreeNode(int v) : data(v), left(nullptr), right(nullptr) {}
};
// 輔助:插入到 BST
TreeNode* bstInsert(TreeNode* root, int val) {
if (!root) return new TreeNode(val);
if (val < root->data) root->left = bstInsert(root->left, val);
else if (val > root->data) root->right = bstInsert(root->right, val);
return root;
}
// 遞迴版 Inorder: Left → Root → Right
void inorderRec(TreeNode* node) {
if (!node) return;
inorderRec(node->left);
cout << node->data << " ";
inorderRec(node->right);
}
// 非遞迴版 Inorder — 手動 Stack
// 模擬 call stack 的行為
void inorderIter(TreeNode* root) {
stack<TreeNode*> s;
TreeNode* cur = root;
while (cur != nullptr || !s.empty()) {
// 一路往左走到底,沿途 push 所有節點
while (cur != nullptr) {
s.push(cur);
cur = cur->left;
}
// pop 最左的節點 → 訪問它
cur = s.top();
s.pop();
cout << cur->data << " ";
// 轉向右子樹
cur = cur->right;
}
}
// 非遞迴版 Preorder — 手動 Stack (Root → Left → Right)
void preorderIter(TreeNode* root) {
if (!root) return;
stack<TreeNode*> s;
s.push(root);
while (!s.empty()) {
TreeNode* cur = s.top();
s.pop();
cout << cur->data << " ";
// 先 push 右再 push 左(因為 stack 是 LIFO,左要先被處理)
if (cur->right) s.push(cur->right);
if (cur->left) s.push(cur->left);
}
}
// 釋放樹
void deleteTree(TreeNode* node) {
if (!node) return;
deleteTree(node->left);
deleteTree(node->right);
delete node;
}
/* ============================================================================
* 9. 反轉字串
*
* 轉換方法:【雙指標(頭尾交換)】
* ============================================================================ */
// 遞迴版 — 交換首尾字元,然後遞迴反轉中間
void reverseRec(string& s, int left, int right) {
if (left >= right) return; // Base case: 中間沒東西了
swap(s[left], s[right]); // 交換首尾
reverseRec(s, left + 1, right - 1); // 遞迴處理中間
}
// 迭代版 — 雙指標從兩端向中間靠攏
void reverseIter(string& s) {
int left = 0, right = (int)s.size() - 1;
while (left < right) {
swap(s[left], s[right]);
++left;
--right;
}
}
/* ============================================================================
* 10. Quick Sort
*
* 轉換方法:【Stack 存待排序的 (low, high) 區間】
* ============================================================================ */
// Partition: 選最後一個元素為 pivot,把小的放左邊、大的放右邊
int partition(vector<int>& a, int low, int high) {
int pivot = a[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (a[j] <= pivot) {
++i;
swap(a[i], a[j]);
}
}
swap(a[i + 1], a[high]);
return i + 1;
}
// 遞迴版
void qsRec(vector<int>& a, int low, int high) {
if (low >= high) return;
int p = partition(a, low, high);
qsRec(a, low, p - 1); // 排序左半
qsRec(a, p + 1, high); // 排序右半
}
// 非遞迴版 — Stack 存待排序區間
void qsIter(vector<int>& a, int low, int high) {
stack<pair<int, int>> s;
s.push({low, high});
while (!s.empty()) {
auto range = s.top();
s.pop();
int lo = range.first;
int hi = range.second;
if (lo >= hi) continue;
int p = partition(a, lo, hi);
// push 兩個子區間(順序無所謂,Stack 會反過來處理)
s.push({lo, p - 1});
s.push({p + 1, hi});
}
}
/* ============================================================================
* 主程式
* ============================================================================ */
int main() {
cout << "============================================================" << endl;
cout << " 遞迴 vs 非遞迴(迭代)完整範例" << endl;
cout << "============================================================\n" << endl;
// ── 1. 階乘 ──
cout << "【1. Factorial 階乘】" << endl;
int n = 10;
cout << " factorialRec(" << n << ") = " << factorialRec(n) << endl;
cout << " factorialIter(" << n << ") = " << factorialIter(n) << endl;
cout << " factorialTail(" << n << ") = " << factorialTail(n) << endl;
cout << endl;
// ── 3. Fibonacci ──
cout << "【3. Fibonacci】" << endl;
n = 20;
cout << " fibRec(" << n << ") = " << fibRec(n) << " [O(2^N) — 慢]" << endl;
cout << " fibIter(" << n << ") = " << fibIter(n) << " [O(N)]" << endl;
// 4. Memoization
n = 40;
memo.assign(n + 1, -1);
cout << " fibMemo(" << n << ") = " << fibMemo(n) << " [O(N) with memo]" << endl;
cout << " fibIter(" << n << ") = " << fibIter(n) << " [O(N)]" << endl;
cout << endl;
// ── 5. Binary Search ──
cout << "【5. Binary Search 二元搜尋】" << endl;
vector<int> sorted = {2, 5, 8, 11, 14, 17, 20, 23, 26, 29};
cout << " Array: ";
for (int x : sorted) cout << x << " ";
cout << endl;
int target = 17;
cout << " bsRec(target=" << target << ") = index " << bsRec(sorted, 0, (int)sorted.size() - 1, target) << endl;
cout << " bsIter(target=" << target << ") = index " << bsIter(sorted, target) << endl;
target = 99;
cout << " bsRec(target=" << target << ") = index " << bsRec(sorted, 0, (int)sorted.size() - 1, target) << endl;
cout << " bsIter(target=" << target << ") = index " << bsIter(sorted, target) << endl;
cout << endl;
// ── 6. GCD ──
cout << "【6. GCD 最大公因數】" << endl;
cout << " gcdRec(1989, 1590) = " << gcdRec(1989, 1590) << endl;
cout << " gcdIter(1989, 1590) = " << gcdIter(1989, 1590) << endl;
cout << endl;
// ── 7. 河內塔 ──
cout << "【7. Tower of Hanoi 河內塔 (n=3)】\n" << endl;
cout << " 遞迴版:" << endl;
hanoiRec(3, 'A', 'C', 'B');
cout << "\n 非遞迴版(手動 Stack):" << endl;
hanoiIter(3, 'A', 'C', 'B');
cout << endl;
// ── 8. 二元樹 Inorder ──
cout << "【8. Binary Tree Inorder 遍歷】" << endl;
TreeNode* tree = nullptr;
int vals[] = {50, 30, 70, 20, 40, 60, 80};
for (int v : vals) tree = bstInsert(tree, v);
/*
* 50
* / \
* 30 70
* / \ / \
* 20 40 60 80
*/
cout << " 遞迴 Inorder: ";
inorderRec(tree);
cout << endl;
cout << " 非遞迴 Inorder: ";
inorderIter(tree);
cout << endl;
cout << " 非遞迴 Preorder: ";
preorderIter(tree);
cout << endl;
deleteTree(tree);
cout << endl;
// ── 9. 反轉字串 ──
cout << "【9. Reverse String 反轉字串】" << endl;
string s1 = "Hello, World!";
string s2 = s1;
reverseRec(s1, 0, (int)s1.size() - 1);
reverseIter(s2);
cout << " 原始: \"Hello, World!\"" << endl;
cout << " 遞迴反轉: \"" << s1 << "\"" << endl;
cout << " 迭代反轉: \"" << s2 << "\"" << endl;
cout << endl;
// ── 10. Quick Sort ──
cout << "【10. Quick Sort 快速排序】" << endl;
vector<int> arr1 = {38, 27, 43, 3, 9, 82, 10};
vector<int> arr2 = arr1;
cout << " 原始: ";
for (int x : arr1) cout << x << " ";
cout << endl;
qsRec(arr1, 0, (int)arr1.size() - 1);
cout << " 遞迴 QS: ";
for (int x : arr1) cout << x << " ";
cout << endl;
qsIter(arr2, 0, (int)arr2.size() - 1);
cout << " 非遞迴 QS: ";
for (int x : arr2) cout << x << " ";
cout << endl;
cout << "\n============================================================" << endl;
cout << " 所有範例完成。" << endl;
cout << "============================================================" << endl;
return 0;
}
相关文章
Algorithms
java
更新于 2026-03-02
#include <iostream>.java
#include <iostream>.java — java source code from the Algorithms learning materials (Algorithms/#include <iostream>.java).
阅读文章 →
Algorithms
cpp
更新于 2026-04-07
748.cpp
748.cpp — cpp source code from the Algorithms learning materials (Algorithms/748.cpp).
阅读文章 →
Algorithms
cpp
更新于 2026-04-07
827.cpp
827.cpp — cpp source code from the Algorithms learning materials (Algorithms/827.cpp).
阅读文章 →
Algorithms
cpp
更新于 2026-04-07
827_best_greedy.cpp
827_best_greedy.cpp — cpp source code from the Algorithms learning materials (Algorithms/827_best_greedy.cpp).
阅读文章 →
Algorithms
cpp
更新于 2026-04-07
8402.cpp
8402.cpp — cpp source code from the Algorithms learning materials (Algorithms/8402.cpp).
阅读文章 →
Algorithms
cpp
更新于 2026-04-07
860.cpp
860.cpp — cpp source code from the Algorithms learning materials (Algorithms/860.cpp).
阅读文章 →