递归之「俄罗斯套娃」的故事

8 阅读3分钟

用一个「俄罗斯套娃」的故事,配上超级简单的代码,帮你秒懂递归!

一、递归是什么?用套娃解释

故事
你收到一个神秘盒子,里面还有盒子,再打开还有盒子... 直到第 5 层盒子,里面有一颗糖果!

规则

  • 打开盒子后,发现里面还有盒子 → 继续打开(重复相同动作)。

  • 直到发现糖果 → 停止(终止条件)。

这就是递归:自己调用自己,直到满足终止条件。

二、递归的核心要素

  1. 终止条件(糖果):

    • 必须有一个明确的终点,否则会无限循环(栈溢出)。
  2. 自我调用(打开下一层盒子):

    • 每次调用时,问题规模变小(如盒子层数减少)。

三、经典递归:计算阶乘(n!)

问题

计算 5!(5 的阶乘)。

递归解法

  • 定义n! = n × (n-1)!

  • 终止条件0! = 1

过程

plaintext

5! = 5 × 4!
     4! = 4 × 3!
          3! = 3 × 2!
               2! = 2 × 1!
                    1! = 1 × 0!
                         0! = 1  ← 终止条件

Java 代码

java

public class Factorial {
    public static int factorial(int n) {
        // 终止条件:0! = 1
        if (n == 0) {
            return 1;
        }
        // 自我调用:n! = n × (n-1)!
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        int result = factorial(5);
        System.out.println("5! = " + result);  // 输出: 120
    }
}

四、递归 vs 循环:用爬楼梯对比

问题

你要爬 3 级楼梯,每次可以走 1 步。有多少种走法?

1. 递归解法

java

public static int climbStairs(int n) {
    // 终止条件:只剩 0 级,只有 1 种走法(不动)
    if (n == 0) {
        return 1;
    }
    // 自我调用:走 1 步后,剩下 n-1 级
    return climbStairs(n - 1);
}

2. 循环解法

java

public static int climbStairsLoop(int n) {
    int result = 1;
    for (int i = 1; i <= n; i++) {
        // 循环计算:1 × 1 × 1 × ... × 1
    }
    return result;
}

对比

递归循环
代码简洁,逻辑清晰性能更高,避免栈溢出
适合问题可拆解的场景适合简单重复的任务

五、递归的执行过程:用函数调用栈理解

故事
你是餐厅服务员,接到一个订单:

  1. 顾客 A 点了汉堡 → 你记录下来(压入栈)。

  2. 做汉堡时,发现需要番茄酱 → 你让厨师 B 去拿(调用新函数)。

  3. 厨师 B 拿番茄酱时,发现需要开瓶器 → 让厨师 C 去拿(嵌套调用)。

  4. 厨师 C 拿到开瓶器 → 返回给 B(函数返回)。

  5. 厨师 B 打开番茄酱 → 返回给你(函数返回)。

  6. 你完成汉堡 → 交给顾客(最终返回)。

函数调用栈

plaintext

main() → climbStairs(3) → climbStairs(2) → climbStairs(1) → climbStairs(0)
                                         ←           ←           ←          ←

六、进阶递归:斐波那契数列

问题

计算斐波那契数列的第 5 项:1, 1, 2, 3, 5, ...

递归解法

  • 定义fib(n) = fib(n-1) + fib(n-2)
  • 终止条件fib(1) = 1fib(2) = 1

Java 代码

java

public class Fibonacci {
    public static int fib(int n) {
        // 终止条件
        if (n == 1 || n == 2) {
            return 1;
        }
        // 自我调用
        return fib(n - 1) + fib(n - 2);
    }

    public static void main(String[] args) {
        int result = fib(5);
        System.out.println("斐波那契第 5 项 = " + result);  // 输出: 5
    }
}

七、递归的优缺点

优点缺点
代码简洁,易理解性能差(重复计算)
适合树形结构或嵌套问题可能导致栈溢出
递归思路自然调试困难

八、递归的优化:记忆化搜索

问题
斐波那契数列递归解法中,fib(3) 被计算了 2 次,造成浪费。

优化
用数组记录已计算的结果,避免重复计算。

Java 代码

java

public static int fibOptimized(int n, int[] memo) {
    // 直接返回已计算的结果
    if (memo[n] != 0) {
        return memo[n];
    }
    // 终止条件
    if (n == 1 || n == 2) {
        memo[n] = 1;
        return 1;
    }
    // 计算并记录结果
    memo[n] = fibOptimized(n - 1, memo) + fibOptimized(n - 2, memo);
    return memo[n];
}

// 调用方式
int[] memo = new int[100];  // 存储中间结果
int result = fibOptimized(5, memo);

九、关键总结

  • 递归 = 函数自己调用自己,直到满足终止条件。

  • 核心:终止条件 + 自我调用(问题规模减小)。

  • 适用场景:树形结构、嵌套问题、分治算法。

记忆口诀

  • 递归就像剥洋葱,一层一层往里剥,直到找到核心!

这样解释够简单了吗?如果还有疑问,随时告诉我! 😊