1. 递归基础概念

递归是方法调用自身的过程,必须包含终止条件和递归步骤

学习目标

理解递归调用栈,掌握递归三要素:终止条件、递归调用、返回值合并

递归执行过程图解

main() → factorial(5)
→ factorial(4)
→ factorial(3)
→ factorial(2)
→ factorial(1)
返回 1
public class RecursionBasic { public static void main(String[] args) { System.out.println("递归开始..."); int result = recursiveMethod(3); System.out.println("最终结果: " + result); } public static int recursiveMethod(int n) { if (n <= 0) { // 终止条件 System.out.println("到达终止条件"); return 0; } System.out.println("当前n值: " + n); return n + recursiveMethod(n - 1); // 递归调用 } }

2. 阶乘案例:5! = 120

通过阶乘计算理解递归的数学基础

1

数学定义

  • 5! = 5 × 4 × 3 × 2 × 1
  • n! = n × (n-1)!
  • 0! = 1(终止条件)
2

代码实现

public class FactorialExample { public static void main(String[] args) { int number = 5; long result = factorial(number); System.out.printf("%d! = %d%n", number, result); } /** * 计算阶乘的递归方法 * @param n 非负整数 * @return n的阶乘 */ public static long factorial(int n) { if (n < 0) { throw new IllegalArgumentException("负数没有阶乘"); } if (n == 0 || n == 1) { // 终止条件 return 1; } System.out.printf("计算 %d × factorial(%d)%n", n, n-1); return n * factorial(n - 1); // 递归调用 } }
调用栈可视化(factorial(5)):
factorial(5) → 5 * factorial(4)
    factorial(4) → 4 * factorial(3)
        factorial(3) → 3 * factorial(2)
            factorial(2) → 2 * factorial(1)
                factorial(1) → 1
                    返回 1
            返回 2 * 1 = 2
        返回 3 * 2 = 6
    返回 4 * 6 = 24
返回 5 * 24 = 120

3. 斐波那契数列:1, 1, 2, 3, 5, 8...

经典递归案例,展示指数级复杂度问题

1

递归定义

  • F(1) = 1, F(2) = 1
  • F(n) = F(n-1) + F(n-2) (n > 2)
2

基础实现(低效)

public class FibonacciBasic { public static int fibonacci(int n) { if (n <= 0) return 0; if (n == 1 || n == 2) return 1; return fibonacci(n-1) + fibonacci(n-2); } public static void main(String[] args) { int n = 8; System.out.println("F(" + n + ") = " + fibonacci(n)); } }
3

优化实现(带缓存)

import java.util.*; public class FibonacciOptimized { private static Map cache = new HashMap<>(); public static long fibonacci(int n) { if (n <= 0) return 0; if (n == 1 || n == 2) return 1; if (cache.containsKey(n)) { return cache.get(n); } long result = fibonacci(n-1) + fibonacci(n-2); cache.put(n, result); return result; } }

4. 目录递归遍历

实际应用:递归遍历文件夹及其子文件

1

问题描述

统计指定目录下所有Java文件的数量和总大小

2

完整实现

import java.io.*; public class DirectoryCounter { private static int javaFileCount = 0; private static long totalSize = 0; public static void main(String[] args) { String path = "./src"; File directory = new File(path); if (!directory.exists() || !directory.isDirectory()) { System.out.println("目录不存在!"); return; } countJavaFiles(directory); System.out.printf("找到 %d 个Java文件,总大小 %.2f KB%n", javaFileCount, totalSize / 1024.0); } private static void countJavaFiles(File dir) { File[] files = dir.listFiles(); if (files == null) return; for (File file : files) { if (file.isDirectory()) { // 递归处理子目录 countJavaFiles(file); } else if (file.getName().endsWith(".java")) { javaFileCount++; totalSize += file.length(); System.out.println("发现: " + file.getAbsolutePath()); } } } }

5. 常见递归错误

90%的递归问题源于这些错误

错误1:缺少终止条件(StackOverflowError)

// 错误代码:无限递归 public static int badFactorial(int n) { return n * badFactorial(n - 1); // 无终止条件! }

错误信息:Exception in thread "main" java.lang.StackOverflowError

错误2:终止条件设置不当

// 错误:终止条件过早 public static int wrongFibonacci(int n) { if (n == 0) return 0; return wrongFibonacci(n-1) + wrongFibonacci(n-2); // 缺少n=1的终止条件,导致F(1)计算错误 }

错误3:参数未收敛

// 错误:参数未减小 public static void printNumbers(int n) { if (n <= 0) return; printNumbers(n); // 参数未变化! }

调试技巧

  • 添加日志打印调用栈深度
  • 使用IDE的调试器单步执行
  • 设置断点观察参数变化
  • 绘制递归树理解流程

性能陷阱

  • 指数级复杂度(如斐波那契)
  • 重复计算相同子问题
  • 过深的调用栈
  • 大量内存消耗

6. 递归优化技巧

将递归转换为更高效的形式

1

尾递归优化

public class TailRecursion { public static long factorialTail(int n) { return factorialHelper(n, 1); } private static long factorialHelper(int n, long accumulator) { if (n == 0) return accumulator; return factorialHelper(n - 1, n * accumulator); // 尾递归 } }
2

迭代替代方案

public static long factorialIterative(int n) { long result = 1; for (int i = 2; i <= n; i++) { result *= i; } return result; }

动手练习

  1. 实现汉诺塔问题的递归解法
  2. 编写二分查找的递归实现
  3. 用递归判断回文字符串
  4. 实现快速排序算法
  5. 比较递归和迭代的性能差异

递归设计口诀

"找重复、找变化、找出口":先找到重复子问题,明确变化的参数,最后确定终止条件。