news 2026/4/15 13:32:23

递归:不止是 “自己调用自己”,看完这篇秒懂

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
递归:不止是 “自己调用自己”,看完这篇秒懂

递归:不止是 “自己调用自己”,看完这篇秒懂

你有没有玩过俄罗斯套娃?打开一个,里面还有一个,再打开,还有一个…… 直到最后一个最小的娃娃出现,游戏才结束。其实在编程世界里,也有这样一种 “套娃式” 算法 —— 递归。它看似绕脑,实则藏着一套简单的逻辑,学会了就能轻松解决很多复杂问题。今天咱们就用几个有趣的案例,带你吃透递归的精髓!

一、递归的本质:从形式到原理

1. 递归的定义

递归是一种算法设计技术,其核心在于方法自己调用自己。在Java中,递归分为两种形式:

  • 直接递归:方法直接调用自身

    csharp

    体验AI代码助手

    代码解读

    复制代码

    public void recursiveMethod() { // 递归调用 recursiveMethod(); }
  • 间接递归:方法A调用方法B,方法B又调用方法A

    csharp

    体验AI代码助手

    代码解读

    复制代码

    public void methodA() { methodB(); } public void methodB() { methodA(); }
2. 递归的原理与栈内存

递归之所以能工作,是因为Java虚拟机(JVM)为每个方法调用分配了栈帧(Stack Frame)。每次递归调用,都会在调用栈中创建一个新的栈帧,存储方法的参数、局部变量和返回地址。

当递归到达终止条件时,栈帧开始依次弹出,计算结果逐层返回。如果递归没有终止条件,栈帧会不断压入,最终导致StackOverflowError

二、递归的三要素:缺一不可

一个正确的递归必须包含以下三个要素:

  1. 递归公式:将大问题分解为小问题的数学表达式
  2. 递归终点:递归停止的条件(终止点)
  3. 递归方向:必须朝着递归终点前进,不能越走越远
2.1 递归公式的理解

递归公式是递归的"灵魂",它定义了如何将问题分解为子问题。例如:

  • 阶乘问题:f(n) = f(n-1) * n
  • 猴子吃桃问题:f(n) = 2 * f(n+1) + 2
2.2 递归终点的重要性

递归终点是防止无限递归的关键。没有终点,递归将永远执行,导致栈溢出。

2.3 递归方向的正确性

递归方向必须确保问题规模逐渐缩小,朝着递归终点靠近。例如:

  • 阶乘问题:从n→n-1→...→1(朝着终点1靠近)
  • 猴子吃桃问题:从1→2→...→10(逆向计算,从第10天往第1天推)

三、递归实战:三个经典案例

案例1:递归计算阶乘
问题描述

计算n的阶乘:n! = 1 × 2 × 3 × ... × n

递归三要素
  • 递归公式f(n) = f(n-1) * n
  • 递归终点f(1) = 1
  • 递归方向:从n→n-1→...→1
代码实现

arduino

体验AI代码助手

代码解读

复制代码

public class RecursionDemo2 { public static void main(String[] args) { System.out.println("5的阶乘:" + f(5)); // 输出120 } public static int f(int n) { if (n == 1) { return 1; // 递归终点 } else { return f(n - 1) * n; // 递归公式 } } }

执行流程

scss

体验AI代码助手

代码解读

复制代码

f(5) = f(4) * 5 f(4) = f(3) * 4 f(3) = f(2) * 3 f(2) = f(1) * 2 f(1) = 1

反向计算:1×2=2 → 2×3=6 → 6×4=24 → 24×5=120

案例2:猴子吃桃问题
问题描述

猴子第一天摘了若干桃子,当即吃了一半+1个;第二天又吃了剩下的一半+1个;以后每天都这样,直到第10天,发现只剩1个桃子了。求猴子第一天摘了多少个。

递归三要素
  • 递归公式f(n) = 2 * f(n+1) + 2(由题意推导得出)
  • 递归终点f(10) = 1(第10天只剩1个桃子)
  • 递归方向:从1→2→...→10(逆向计算,从第10天往第1天推)
代码实现

arduino

体验AI代码助手

代码解读

复制代码

public class RecursionDemo3 { public static void main(String[] args) { System.out.println("猴子第一天摘的桃子数:" + f(1)); // 输出1534 } public static int f(int n) { if (n == 10) { return 1; // 递归终点 } return 2 * f(n + 1) + 2; // 递归公式 } }

问题解析

原题描述的是正向过程(第1天→第10天),但递归更适合从后往前推(第10天→第1天)。通过递归公式,我们不需要知道每天吃多少,只需知道第n天的桃子数与第n+1天的关系,就可以从第10天开始反推第1天。

案例3:递归遍历文件系统
问题描述

在D盘下搜索"QQ.exe"文件,并输出其路径。

递归三要素
  • 递归公式:遍历当前目录下的所有文件和文件夹
  • 递归终点:没有更多文件或文件夹需要遍历
  • 递归方向:从根目录开始,逐层深入子目录
代码实现

scss

体验AI代码助手

代码解读

复制代码

import java.io.File; import java.io.IOException; public class FileSearchTest4 { public static void main(String[] args) { File d盘 = new File("D:\"); // 搜索根目录 try { searchFile(d盘, "QQ.exe"); // 调用递归搜索方法 } catch (IOException e) { e.printStackTrace(); } } /** * 递归搜索文件 * @param dir 搜索的目录 * @param fileName 要找的文件名 */ public static void searchFile(File dir, String fileName) throws IOException { // 处理异常情况:目录不存在、是文件、为空 if (dir == null || !dir.exists() || dir.isFile()) { return; } // 获取当前目录下的所有文件/文件夹 File[] files = dir.listFiles(); if (files != null && files.length > 0) { for (File file : files) { if (file.isFile()) { // 是文件,判断名称是否匹配 if (file.getName().contains(fileName)) { System.out.println("找到文件:" + file.getAbsolutePath()); // 直接打开文件(可选) Runtime.getRuntime().exec(file.getAbsolutePath()); } } else { // 是文件夹,递归进入继续搜索 searchFile(file, fileName); } } } } }

优势分析

递归遍历文件系统比使用多层循环更加简洁、优雅。它自动处理了文件夹的嵌套结构,无需手动管理遍历的层级。

四、递归 vs 迭代:如何选择?

4.1 递归的优势
  • 代码简洁,逻辑清晰
  • 适合处理层次结构问题(如文件系统、树形结构)
  • 适合解决可以分解为子问题的问题(如阶乘、斐波那契数列)
4.2 递归的劣势
  • 额外的栈内存消耗
  • 递归深度过大可能导致栈溢出
  • 对于简单问题,迭代通常更高效
4.3 何时使用递归?
  • 问题可以自然地分解为相同类型的子问题
  • 有明确的递归终点
  • 问题规模不会太大(避免栈溢出)

五、递归的常见误区与优化

5.1 误区:没有终止条件

csharp

体验AI代码助手

代码解读

复制代码

public static void printA() { System.out.println("A"); printA(); // 没有终止条件,会导致StackOverflowError }

5.2 误区:递归方向错误

arduino

体验AI代码助手

代码解读

复制代码

// 错误:递归方向错误,会导致无限递归 public static int f(int n) { if (n == 1) { return 1; } return f(n + 1) * n; // n+1,越来越远离终点 }

5.3 优化:尾递归优化

在某些语言中(如Scala、Scheme),尾递归可以被优化为迭代,避免栈帧累积。但在Java中,尾递归不被优化,所以需要谨慎使用。

arduino

体验AI代码助手

代码解读

复制代码

// 尾递归示例(Java中不被优化) public static int factorial(int n, int accumulator) { if (n == 0) { return accumulator; } return factorial(n - 1, n * accumulator); }

六、总结

递归是一种强大的编程技术,它通过"问题分解"和"递归终点"的组合,以简洁的代码解决复杂问题。要掌握递归,关键在于理解并正确应用递归的三要素:递归公式、递归终点和递归方向。

  • 递归公式定义了如何将大问题分解为小问题
  • 递归终点是防止无限递归的关键
  • 递归方向确保问题规模逐渐缩小

⚠️ 小提醒:。在实际应用中,递归特别适合处理层次结构问题(如文件系统、树形结构)和可以自然分解的问题(如阶乘、斐波那契数列),但也要注意,递归不是万能的,递归虽然简洁,但会占用额外的栈内存,所以不要用它解决简单问题(比如求 1+2+3),对于简单问题或大规模数据,迭代可能更为高效。

掌握递归,不仅能让你的代码更加优雅,还能提升你解决复杂问题的能力。希望这篇文章能帮助你更好地理解和应用递归!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 18:03:42

告别“大海捞针“:InternLM2.5-1M如何让百万字长文本变得触手可及?

还记得那个让你抓狂的场景吗?一份200页的合同摆在眼前,你需要在密密麻麻的条款中找出那个关键的风险点。或者面对上百篇学术论文,想要快速梳理出核心观点却无从下手。现在,这些困扰将成为过去式。 【免费下载链接】InternLM Offic…

作者头像 李华
网站建设 2026/4/10 20:07:34

如何快速解决PyTorch Geometric TUDataset加载问题:5个实战技巧

如何快速解决PyTorch Geometric TUDataset加载问题:5个实战技巧 【免费下载链接】pytorch_geometric Graph Neural Network Library for PyTorch 项目地址: https://gitcode.com/GitHub_Trending/py/pytorch_geometric PyTorch Geometric TUDataset是图神经网…

作者头像 李华
网站建设 2026/4/14 1:17:54

BetterDiscord 深度定制指南:打造属于你的专属聊天体验

BetterDiscord 深度定制指南:打造属于你的专属聊天体验 【免费下载链接】BetterDiscordApp Better Discord App enhances Discord desktop app with new features. 项目地址: https://gitcode.com/gh_mirrors/be/BetterDiscordApp 你是否曾经觉得 Discord 的…

作者头像 李华
网站建设 2026/4/15 4:00:18

BUUCTF[jarvisoj_level2_x64]

步骤使用checksec查看使用ida(pro)打开根进vulnerable_function函数依旧是栈溢出这道题我们无法使用上一题的32位来直接使用plt表来跳转到system,所以我们使用ROPgadget使用指令 ROPgadget --binary 文件名 来获取信息这里可以查看到pop rdi;ret的地址在64位中前几个参数我们是…

作者头像 李华
网站建设 2026/4/14 12:47:05

前端如何通过FormData实现大文件分片上传?

网工大三党文件上传救星:原生JS实现10G大文件上传(Vue3IE8兼容) 兄弟,作为刚入坑网络工程的山西老狗,我太懂你现在的处境了——老师要10G大文件上传的毕业设计,网上找的代码全是“断头路”,后端…

作者头像 李华
网站建设 2026/4/11 4:39:32

.NET WebForm如何支持大文件上传的进度显示?

毕业设计:企业级文件传输系统方案探索 在毕业设计选题阶段,我结合自身兴趣与计算机专业所学,确定了企业级文件传输系统这一课题。近期,我在网上搜索相关资料、参与论坛交流、加入多个QQ群和微信群,但收获不尽如人意。很…

作者头像 李华