第39章

Java递归

掌握递归思想与算法设计

学习目标

Java递归概述

递归是一种强大的编程技术,其中函数调用自身来解决更小规模的相同问题。递归在算法设计、数据结构操作和问题求解中发挥着重要作用。

递归的核心要素

  • 基础情况(Base Case):递归的终止条件,防止无限递归
  • 递归情况(Recursive Case):函数调用自身的情况
  • 问题分解:将大问题分解为相同类型的小问题
  • 结果合并:将子问题的结果合并得到最终答案

递归执行流程

问题输入 检查基础情况 分解子问题 递归调用 合并结果

递归类型详解

线性递归

阶乘计算示例:
public static long factorial(int n) {
    if (n <= 1) {
        return 1; // 基础情况
    }
    return n * factorial(n - 1); // 递归情况
}
  • 每次递归调用产生一个子问题
  • 调用栈深度等于问题规模
  • 适用于序列处理问题
  • 时间复杂度通常为O(n)

二分递归

斐波那契数列示例:
public static long fibonacci(int n) {
    if (n <= 1) {
        return n; // 基础情况
    }
    return fibonacci(n-1) + fibonacci(n-2);
}
  • 每次递归调用产生两个子问题
  • 形成二叉树状的调用结构
  • 可能存在重复计算问题
  • 需要考虑优化策略

多分递归

汉诺塔问题示例:
public static void hanoi(int n, char from, 
                        char aux, char to) {
    if (n == 1) {
        System.out.println("Move " + from + " to " + to);
        return;
    }
    hanoi(n-1, from, to, aux);
    hanoi(1, from, aux, to);
    hanoi(n-1, aux, from, to);
}
  • 每次递归调用产生多个子问题
  • 问题分解更加复杂
  • 适用于复杂的组合问题
  • 需要仔细设计递归策略

递归 vs 迭代

递归实现

// 递归求和
public static int sum(int n) {
    if (n <= 0) return 0;
    return n + sum(n - 1);
}
  • 优点:代码简洁,逻辑清晰
  • 优点:自然表达分治思想
  • 缺点:可能栈溢出
  • 缺点:函数调用开销

迭代实现

// 迭代求和
public static int sum(int n) {
    int result = 0;
    for (int i = 1; i <= n; i++) {
        result += i;
    }
    return result;
}
  • 优点:空间效率高
  • 优点:不会栈溢出
  • 缺点:某些问题实现复杂
  • 缺点:可读性可能较差

递归复杂度分析

理解递归算法的时间和空间复杂度对于选择合适的算法实现至关重要:

算法 时间复杂度 空间复杂度 特点
阶乘(递归) O(n) O(n) 线性递归,调用栈深度为n
斐波那契(朴素递归) O(2^n) O(n) 指数时间,存在大量重复计算
斐波那契(记忆化) O(n) O(n) 通过缓存避免重复计算
二分查找(递归) O(log n) O(log n) 每次递归问题规模减半
快速排序(递归) O(n log n) O(log n) 分治算法,平均情况
汉诺塔 O(2^n) O(n) 指数时间,最优解

递归优化技术

记忆化(Memoization)

记忆化斐波那契:
private static Map cache = new HashMap<>();

public static long fibonacciMemoized(int n) {
    if (n <= 1) return n;
    
    if (cache.containsKey(n)) {
        return cache.get(n);
    }
    
    long result = fibonacciMemoized(n-1) + fibonacciMemoized(n-2);
    cache.put(n, result);
    return result;
}

尾递归优化

尾递归阶乘:
public static long factorialTail(int n) {
    return factorialHelper(n, 1);
}

private static long factorialHelper(int n, long acc) {
    if (n <= 1) return acc;
    return factorialHelper(n - 1, n * acc);
}

动态规划转换

动态规划斐波那契:
public static long fibonacciDP(int n) {
    if (n <= 1) return n;
    
    long[] dp = new long[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];
}

在线代码体验

点击下面的按钮,在在线IDE中查看和运行完整的递归示例代码:

💻 查看完整代码 - 在线IDE体验

递归最佳实践

推荐做法

  • 明确基础情况
    确保递归有明确的终止条件
  • 确保收敛性
    每次递归都向基础情况靠近
  • 使用记忆化
    避免重复计算,提高效率
  • 考虑栈深度
    深度过大时考虑迭代实现
  • 清晰的函数设计
    参数和返回值要明确

避免的做法

  • 缺少基础情况
    会导致无限递归和栈溢出
  • 忽略性能问题
    不考虑时间和空间复杂度
  • 过度使用递归
    简单问题不需要递归
  • 不处理边界情况
    忽略特殊输入的处理
  • 递归深度过大
    不考虑栈溢出风险

递归应用场景

递归适用的问题特征

  • 问题可以分解为相同类型的子问题
  • 子问题的解可以合并为原问题的解
  • 存在明确的基础情况
  • 问题具有最优子结构性质

数据结构操作

算法设计

数学问题

章节总结