一文看清递归的本质(图文并茂)

递归的本质

PixPin_2025-03-30_10-40-58.png

一个求和理解过程

/* 递归 */
int recur(int n) {
    
    
    // 终止条件
    if (n == 1)
        return 1;
    // 递:递归调用
    int res = recur(n - 1);
    // 归:返回结果
    return n + res;
}

PixPin_2025-03-30_10-39-42.png

为什么要用递归?

查找文件

PixPin_2025-03-30_11-15-54.png
递归代码(简洁优雅):

/**
 * 递归方式实现文件搜索
 * 优势:代码简洁,直接反映树形结构的遍历逻辑
 * @param dir 当前要搜索的目录对象
 */
void search(File dir) {
    
    
    // 获取当前目录下的所有文件和子目录
    for (File f : dir.listFiles()) {
    
    
        // 如果是文件,则进行检查处理
        if (f.isFile()) {
    
    
            checkFile(f); // 对文件进行自定义检查(如文件名匹配等)
        }
        // 如果是目录,则递归调用自身进行搜索
        else if (f.isDirectory()) {
    
    
            search(f); // 递归调用,自动处理任意深度的目录层级
        }
    }
}

循环代码(维护复杂):

/**
 * 迭代方式处理当前目录对象(与递归参数语义完全一致)
 * @param currentDir 当前要处理的目录对象
 */
void iterativeSearch(File currentDir) {
    
    
    // 参数校验
    if (currentDir == null || !currentDir.isDirectory()) {
    
    
        return; // 与递归版本行为一致,非法输入直接返回
    }
    // 使用双端队列作为栈(实现深度优先搜索)
    Deque<File> dirStack = new ArrayDeque<>();
    dirStack.push(currentDir); // 初始放入当前目录
    while (!dirStack.isEmpty()) {
    
    
        File dir = dirStack.pop();
        File[] children = dir.listFiles();
        if (children == null) continue; // 处理无权限情况
        for (File f : children) {
    
    
            if (f.isFile()) {
    
    
                checkFile(f); // 文件处理
            } else if (f.isDirectory()) {
    
    
                dirStack.push(f); // 子目录入栈(后续处理)
            }
        }
    }
}

扩展

调用栈

递归每次调用本身,都会在系统内存新建开一个空间存储。可能会导致:

  1. 比普通循环更耗费内存空间
  2. 递归调用函数会产生额外开销,时间效率一般也会更低
递归树

一个调用产生了两个调用分支。这样不断递归调用下去, 最终将产生一棵层数为 n 的递归树(recursion tree)。
当处理与“分治”相关的算法问题时,递归往往比迭代的思路更加直观、代码更加易读。以“斐波那契数列”为例。代码如下:

/* 斐波那契数列:递归 */
int fib(int n) {
    
    
    // 终止条件 f(1) = 0, f(2) = 1
    if (n == 1 || n == 2)
        return n - 1;
    // 递归调用 f(n) = f(n-1) + f(n-2)
    int res = fib(n - 1) + fib(n - 2);
    // 返回结果 f(n)
    return res;
}

递归树如下:
PixPin_2025-03-30_11-24-48.png