递归(Recursion)

递归是一种通过函数调用自身来解决问题的方法,理解递归最有效的方法是从「树」的角度去抽象和分析。

定义

递归(Recursion)是指一个函数直接或间接调用自身来解决问题的编程技巧。递归算法本质上是一种穷举手段,通过把问题分解成更小的子问题来解决复杂问题。

核心观点:理解和编写递归算法最有效的方法是从「树」的视角去理解,而非从栈、镜像等其他角度。

核心特性

特性 说明
自引用 函数直接或间接调用自身
终止条件 必须有 base case 防止无限递归
问题分解 将大问题分解为相似的子问题
树形结构 递归调用可以抽象为一棵递归树

两种思维模式

编写递归算法只有两种思维模式,都尝试套用一下,必然有一种能写出来:

1. 分解问题(Divide & Conquer)

特点

  • 递归函数有清晰的定义返回值
  • 通过子问题的解推导原问题的解
  • 类似数学归纳法的思想

适用场景斐波那契数列、二叉树最大深度、动态规划问题

代码示例(斐波那契数列):

// 定义:输入一个非负整数 n,返回斐波那契数列中的第 n 个数
int fib(int n) {
    if (n < 2) {
        return n;
    }
    // 利用定义,计算子问题
    int fib_n_1 = fib(n - 1);
    int fib_n_2 = fib(n - 2);
    // 通过子问题的解,计算原问题的解
    return fib_n_1 + fib_n_2;
}

可视化理解

  • 递归树上的每个节点就是一个子问题的解
  • 父节点等待子节点计算完成,然后合并结果

2. 遍历(Traverse)

特点

  • 递归函数无返回值,单纯遍历
  • 通过外部变量(如全局变量)收集结果
  • 类似 for 循环的递归版本

适用场景:全排列、组合问题、回溯算法、DFS

代码示例(全排列):

List<List<Integer>> res = new LinkedList<>();
List<Integer> track = new LinkedList<>();

void backtrack(int[] nums, List<Integer> track) {
    if (track.size() == nums.length) {
        // 到达叶子节点,收集结果
        res.add(new LinkedList<>(track));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        // 做选择
        track.add(nums[i]);
        backtrack(nums, track);
        // 撤销选择
        track.removeLast();
    }
}

可视化理解

  • 递归树上的节点记录之前的选择
  • 所有合法结果都在叶子节点上

从树的角度理解递归

为什么是树?

任何递归算法都可以抽象成一棵树结构:

  • 斐波那契数列 → 二叉树(每个节点有两个子节点:fib(n-1) 和 fib(n-2))
  • 全排列 → 多叉树(每个节点有多个子节点,对应每个可选元素)
  • 二叉树遍历 → 本身就是树结构

经典递归算法

算法 思维模式 说明
斐波那契数列 分解问题 fib(n) = fib(n-1) + fib(n-2)
二叉树最大深度 分解问题 / 遍历 两种思路都可以解决
全排列 遍历 回溯法收集所有叶子节点
归并排序 分解问题 后序遍历:先排左右,再合并
快速排序 分解问题 前序遍历:先排好一个元素,再排左右
二叉树遍历 遍历 前序/中序/后序位置的应用

与类似概念对比

维度 递归 迭代
实现方式 函数调用自身 循环结构
本质 穷举(遍历递归树) 穷举(按规则迭代)
空间开销 递归栈 O(深度) 通常 O(1)
可读性 通常更简洁 可能更复杂
适用场景 树形问题、分治问题 线性问题、状态简单问题
维度 分解问题思维 遍历思维
函数返回值 有,返回子问题解 无,用外部变量收集
类似模式 动态规划、分治 回溯、DFS
理解方式 数学归纳法 遍历/循环

注意事项

  • 必须有 base case:否则会无限递归导致栈溢出
  • 避免重复计算:如斐波那契递归有大量重复子问题,可用记忆化优化
  • 递归深度限制:过深的递归可能导致栈溢出,可考虑改为迭代
  • 副作用:遍历思维中用外部变量可能产生副作用,需注意状态管理

应用场景

  • 树形结构问题:二叉树、N叉树、DOM树等
  • 分治问题:归并排序、快速排序、二分查找
  • 组合枚举问题:全排列、组合、子集
  • 动态规划问题斐波那契数列、背包问题、最长公共子序列

相关概念

参考资料


Interactive Graph