递归的本质是**“把大问题拆成与原问题结构相同的小问题,直到小问题可以直接解决”**。它包含两个关键要素:
举个生活例子:俄罗斯套娃——打开一个大娃娃,里面是一个小一号的同类娃娃,直到最小的娃娃(基线条件)被打开,整个过程就是递归。
C语言递归通过调用栈(系统自动管理的栈结构)实现:每次递归调用时,当前函数的参数、局部变量和返回地址会被压入栈;当递归到基线条件并返回时,栈顶元素依次弹出,恢复到上一层函数的执行状态。
n! 的递归过程阶乘定义:n! = n × (n-1)!(n ≥ 1),0! = 1(基线条件)。
递归函数实现:
int factorial(int n) {
if (n == 0) return 1; // 基线条件:0! = 1
return n * factorial(n - 1); // 递归条件:n! = n × (n-1)!
} 调用 factorial(3) 的执行流程(栈的变化):
factorial(3) → 压栈(n=3),执行 3 * factorial(2);factorial(2) → 压栈(n=2),执行 2 * factorial(1);factorial(1) → 压栈(n=1),执行 1 * factorial(0);factorial(0) → 压栈(n=0),触发基线条件,返回 1;factorial(0) → 回到 factorial(1),计算 1 * 1 = 1,返回 1;factorial(1) → 回到 factorial(2),计算 2 * 1 = 2,返回 2;factorial(2) → 回到 factorial(3),计算 3 * 2 = 6,返回 6。最终结果为 6(即 3! = 6)。可见,递归的“进栈”是不断分解问题,“出栈”是合并子问题的解。
定义:fib(n) = fib(n-1) + fib(n-2)(n ≥ 2),fib(0)=0,fib(1)=1(基线条件)。
递归实现(直观但有缺陷):
int fib(int n) {
if (n == 0) return 0; // 基线条件1
if (n == 1) return 1; // 基线条件2
return fib(n - 1) + fib(n - 2); // 递归条件
} 缺陷:存在大量重复计算(如 fib(5) 会重复计算 fib(3)、fib(2) 多次),时间复杂度为 O(2ⁿ),效率低。实际开发中常用迭代或备忘录法优化。
规则:有3根柱子(A、B、C)和n个盘子(从小到大叠在A上),需将所有盘子从A移到C,移动时大盘不能压小盘,每次只能移一个盘子。
递归思路:
递归实现:
#include <stdio.h>
// 函数功能:将n个盘子从from柱经aux柱移到to柱
void hanoi(int n, char from, char aux, char to) {
if (n == 1) { // 基线条件:只剩1个盘子,直接移动
printf("Move disk %d from %c to %c\n", n, from, to);
return;
}
// 递归条件:分解为3步
hanoi(n - 1, from, to, aux); // 步骤1:n-1个盘子从from→aux(借助to)
printf("Move disk %d from %c to %c\n", n, from, to); // 步骤2:最大盘from→to
hanoi(n - 1, aux, from, to); // 步骤3:n-1个盘子从aux→to(借助from)
}
int main() {
int n = 3; // 3个盘子
hanoi(n, 'A', 'B', 'C'); // 从A经B移到C
return 0;
} 输出(3个盘子的移动步骤):
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C 二叉树节点定义:
typedef struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode; 先序遍历(根→左→右)的递归实现:
// 先序遍历:根节点→左子树→右子树
void preorder(TreeNode *root) {
if (root == NULL) return; // 基线条件:空树,直接返回
printf("%d ", root->val); // 访问根节点
preorder(root->left); // 递归遍历左子树
preorder(root->right); // 递归遍历右子树
} 递归的优雅之处在于:无需手动维护栈来追踪遍历位置,函数调用栈自动完成了“回溯”逻辑。
n 递减至基线条件),否则无法终止;ulimit -s 查看栈大小,Linux下默认通常为8MB)。递归和迭代(循环)是解决重复问题的两种方式,各有适用场景:
例:阶乘的迭代实现(效率更高):
int factorial_iter(int n) {
int res = 1;
for (int i = 1; i <= n; i++) {
res *= i;
}
return res;
}