Appearance
L5_06 递归算法
一、递归算法概述
递归是一种编程技巧,指函数在其定义中调用自身的过程。递归算法将复杂问题分解为更小的、相似的子问题,直到子问题足够简单,可以直接求解。
1.1 递归的基本结构
cpp
返回类型 函数名(参数) {
// 基本情况(递归终止条件)
if (基本条件) {
return 基础值;
}
// 递归情况(调用自身)
return 函数名(更小的参数);
}1.2 递归的特点
优点:
- 代码简洁,逻辑清晰
- 易于理解和实现
- 适合解决分治问题
缺点:
- 可能导致栈溢出
- 时间和空间复杂度较高
- 可能存在重复计算
二、递归的经典示例
2.1 阶乘计算
cpp
int factorial(int n) {
// 基本情况
if (n == 0 || n == 1) {
return 1;
}
// 递归情况
return n * factorial(n - 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
= 1202.2 斐波那契数列
cpp
int fibonacci(int n) {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}2.3 求最大公约数(欧几里得算法)
cpp
int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}三、递归的时间和空间复杂度
3.1 时间复杂度分析
递归的时间复杂度通常通过递归树来分析。对于一个递归函数,如果每次调用会产生 k 个子问题,且递归深度为 n,则时间复杂度为 O(k^n)。
3.2 空间复杂度分析
递归的空间复杂度主要由调用栈的深度决定。如果递归深度为 n,每个栈帧占用 O(1) 的空间,则空间复杂度为 O(n)。
3.3 示例分析
cpp
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n-1) + fibonacci(n-2);
}时间复杂度:O(2^n) - 指数级 空间复杂度:O(n) - 线性级(递归栈深度)
四、递归的优化策略
4.1 记忆化搜索
通过存储已经计算过的结果,避免重复计算。
cpp
int memo[1000];
int fibonacciMemo(int n) {
if (n <= 1) return n;
if (memo[n] != 0) return memo[n];
memo[n] = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
return memo[n];
}优化后的时间复杂度:O(n)
4.2 动态规划
将递归转换为迭代,使用数组存储中间结果。
cpp
int fibonacciDP(int n) {
if (n <= 1) return n;
vector<int> dp(n + 1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; ++i) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}4.3 尾递归优化
尾递归是指递归调用是函数的最后一个操作。某些编译器可以对尾递归进行优化,将其转换为迭代。
cpp
int factorialTail(int n, int accumulator) {
if (n == 0) return accumulator;
return factorialTail(n - 1, n * accumulator);
}
int factorial(int n) {
return factorialTail(n, 1);
}五、递归的应用场景
5.1 树的遍历
cpp
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
void preorder(TreeNode* root) {
if (root == nullptr) return;
cout << root->val << " ";
preorder(root->left);
preorder(root->right);
}5.2 图的深度优先搜索
cpp
void dfs(int node, vector<bool>& visited, const vector<vector<int>>& graph) {
visited[node] = true;
cout << node << " ";
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
dfs(neighbor, visited, graph);
}
}
}5.3 排列组合
cpp
void permute(vector<int>& nums, int start, vector<vector<int>>& result) {
if (start == nums.size()) {
result.push_back(nums);
return;
}
for (int i = start; i < nums.size(); ++i) {
swap(nums[start], nums[i]);
permute(nums, start + 1, result);
swap(nums[start], nums[i]); // 回溯
}
}六、递归与分治
分治算法通常采用递归实现:
- 分解:将问题分解为若干个规模较小的子问题
- 解决:递归地解决每个子问题
- 合并:将子问题的解合并为原问题的解
cpp
int divideAndConquer(vector<int>& arr, int left, int right) {
if (left == right) {
return arr[left];
}
int mid = left + (right - left) / 2;
int leftResult = divideAndConquer(arr, left, mid);
int rightResult = divideAndConquer(arr, mid + 1, right);
return combine(leftResult, rightResult);
}七、递归的注意事项
- 必须有终止条件:否则会导致无限递归,栈溢出
- 避免重复计算:使用记忆化或动态规划优化
- 注意栈溢出风险:递归深度不宜过大
- 考虑尾递归优化:某些语言和编译器支持尾递归优化
八、总结
递归是一种强大的编程技巧,能够将复杂问题简化为相似的子问题。掌握递归思想对于学习分治、动态规划等高级算法至关重要。
