当前位置: 首页 > article >正文

数据结构:递归(Recursion)

目录

示例1:先打印,再递归

 示例2:先递归,再打印

递归的两个阶段 

 递归是如何使用栈内存

复杂度分析

递归中的静态变量 

内存结构图解


递归:函数调用自己 + 必须有判断条件来使递归继续或停止

我们现在通过这两个示例代码,用“递归调用树”的方式,一步步直观分析,并进行对比。

示例1:先打印,再递归

void fun(int x)
{if(x > 0){printf("%d", x);fun(x - 1);}
}int main()
{int x = 3;fun(x);
}

执行流程(调用栈顺序) 

main -> fun(3)|--> print 3--> fun(2)|--> print 2--> fun(1)|--> print 1--> fun(0) -> 终止

调用树结构

fun(3)└── print 3└── fun(2)└── print 2└── fun(1)└── print 1└── fun(0) [结束]

输出结果

321

 

 示例2:先递归,再打印

void fun(int x)
{if(x > 0){fun(x - 1);printf("%d", x);}
}int main()
{int x = 3;fun(x);
}

执行流程(调用栈顺序)

main -> fun(3)|--> fun(2)|--> fun(1)|--> fun(0) [终止]<-- print 1<-- print 2<-- print 3

调用树结构

fun(3)└── fun(2)└── fun(1)└── fun(0) [结束]└── print 1└── print 2└── print 3

输出结果

123

 


 

递归的两个阶段 

递归包含两个阶段:calling(上升阶段 / Ascending)和returning(下降阶段 / Descending)

void fun(int x)
{if(x > 0){// calling,又叫 Ascendingfun(x - 1);  // 如果这里有其他操作(例如 fun(x-1)*2),则属于 returning// returning,又叫 Descending}
}

我们假设执行 fun(3)

🌿 调用过程(Calling / Ascending):

fun(3)
└── fun(2)└── fun(1)└── fun(0)  // 到这里终止(因为 x > 0 不成立)

这部分就是不断往下递归调用自己的过程,称为“上升阶段”,因为递归在“深入栈底”,一层一层地压入调用栈中。

在这个阶段,“程序在挖坑”,但并没有开始“填坑”。

🌀 返回过程(Returning / Descending)

fun(0) 终止后,每一层函数从栈中返回: 

fun(0) 返回到 fun(1)
→ fun(1) 返回到 fun(2)
→ fun(2) 返回到 fun(3)
→ fun(3) 返回到 main

这个阶段称为“下降阶段”,也可以叫returning,因为每一次递归调用返回时都会执行“fun(x - 1)”后面的语句(如果有的话)。 

 


 递归是如何使用栈内存

在 C 语言中,每一次函数调用都会在内存中开辟一个栈帧(Stack Frame),用来保存该函数的局部变量、参数、返回地址等。

当你调用递归函数 fun(x) 时,每次调用都会压入(push)一个新的栈帧,等函数返回后再弹出(pop)这个栈帧。

我们以下面的代码为例:

void fun(int x)
{if(x > 0){printf("%d", x);fun(x - 1);}
}

✅ 第一步:main() 调用 fun(3)

+------------------------+ ← 栈顶(最新压栈)
| fun(x=3) 的栈帧         | ← 保存参数 x=3,返回地址
+------------------------+
| main() 的栈帧           |
+------------------------+

✅ 第二步:进入 fun(2)(x=2)

+------------------------+
| fun(x=2) 的栈帧         |
+------------------------+
| fun(x=3) 的栈帧         |
+------------------------+
| main() 的栈帧           |
+------------------------+

✅ 第三步:进入 fun(1)(x=1)

+------------------------+
| fun(x=1) 的栈帧         |
+------------------------+
| fun(x=2) 的栈帧         |
+------------------------+
| fun(x=3) 的栈帧         |
+------------------------+
| main() 的栈帧           |
+------------------------+

✅ 第四步:进入 fun(0)(不执行递归)

+------------------------+
| fun(x=0) 的栈帧         | ← 条件不成立,直接返回
+------------------------+
| fun(x=1) 的栈帧         |
| fun(x=2) 的栈帧         |
| fun(x=3) 的栈帧         |
| main() 的栈帧           |
+------------------------+

⬅️ 之后依次弹栈回到 main(),释放这些栈帧。


 

复杂度分析

✅ 时间复杂度:O(n)

我们要分析的是:fun(n) 一共要花费多少单位时间?

  • 执行一次 if (x > 0) :1 单位时间

  • 执行一次 printf("%d", x) :1 单位时间

  • 执行一次 fun(x - 1) :记作 T(n-1) 单位时间(递归调用自身的耗时)

所以,每次调用 fun(x) 的总时间 T(n) 是:

T(n) = 1(if 判断)+ 1(打印)+ T(n - 1)
T(n) = 1 + 1 + T(n-1)= 2 + T(n-1)T(n-1) = 2 + T(n-2)
T(n-2) = 2 + T(n-3)
...
T(1) = 2 + T(0)
T(0) = 1(只执行一次 if,条件不成立直接返回)

 所以:

T(n) = 2n + 1
名称表达式说明
总时间函数T(n) = 2n + 1包括每次调用的 if 和 printf,共 2n 次操作 + 1 次 base case
时间复杂度O(n)取最高阶忽略常数,线性复杂度

✅ 空间复杂度(栈空间):O(n)

  • 每次递归调用会创建一个新的栈帧。

  • 所以最多有 n+1 层递归(从 x = n 到 x = 0)

  • 所以空间复杂度也是:O(n)


 

递归中的静态变量 

int fun(int n)
{static int x = 0;  // 静态变量,只初始化一次,保存在**静态区(数据段)**if(n > 0){x++;return fun(n - 1) + x;}return 0;
}

普通局部变量:

  • 每次函数调用都会新建一个副本,存在栈区

  • 调用结束后销毁,不会“记住”上一次的值

 静态局部变量 static

  • 生命周期为整个程序运行期间

  • 只初始化一次

  • 存储在静态存储区(Data Segment),不在栈区

  • 所以每次递归调用都共享同一个 x

递归过程树 

                      fun(5)|vfun(4) + x=1 → return ?|vfun(3) + x=2 → return ?|vfun(2) + x=3 → return ?|vfun(1) + x=4 → return ?|vfun(0)       → return 0开始回溯:
fun(1): return 0 + x=5 = 5
fun(2): return 5 + x=5 = 10
fun(3): return 10 + x=5 = 15
fun(4): return 15 + x=5 = 20
fun(5): return 20 + x=5 = 25

 

 

❗ 注意:x 在每次递归调用时都自增,最终值是 x=5

而因为每次回溯时都加的是 当前 x=5,所以:

最终返回值 = 5 + 5 + 5 + 5 + 5 = 25

内存结构图解

+---------------------------+ ← 低地址
| 代码区(Text)             | ← 编译后的 fun() / main() 指令
+---------------------------+
| 数据段(Static / Global)  |
| static int x = 0;         | ← 所有递归共享这一个变量
+---------------------------+
| 栈区(Stack)              |
| fun(n=1) 的栈帧            |
| fun(n=2) 的栈帧            |
| fun(n=3) 的栈帧            |
| fun(n=4) 的栈帧            |
| fun(n=5) 的栈帧            |
| main() 的栈帧              |
+---------------------------+
|                           |
| 堆区(Heap)               | ← malloc / new 用
+---------------------------+ ← 高地址

相关文章:

数据结构:递归(Recursion)

目录 示例1&#xff1a;先打印&#xff0c;再递归 示例2&#xff1a;先递归&#xff0c;再打印 递归的两个阶段 递归是如何使用栈内存 复杂度分析 递归中的静态变量 内存结构图解 递归&#xff1a;函数调用自己 必须有判断条件来使递归继续或停止 我们现在通过这两个示…...

Cesium快速入门到精通系列教程一:打造第一个Cesium应用

一、打造第一个Cesium应用 1、官方渠道下载Cesium&#xff08;可选择历史版本&#xff09; ​​GitHub Releases页面​​&#xff1a;https://github.com/CesiumGS/cesium/releases 访问 Cesium GitHub Releases&#xff0c;此处列出了所有正式发布的版本。 通过标签&#…...

力扣题解106:从中序与后序遍历序列构造二叉树

一、题目内容 题目要求根据二叉树的中序遍历序列和后序遍历序列来重建二叉树。具体来说&#xff0c;我们需要利用中序遍历序列和后序遍历序列的特点&#xff0c;通过递归的方法逐步构建出完整的二叉树。 中序遍历序列的特点是&#xff1a;左子树 -> 根节点 -> 右子树。后…...

Vue传参Props还是Pinia

Pinia 适用场景 全局状态管理 多个不相关组件需要共享数据需要跨页面/路由共享状态 复杂状态逻辑 包含多个相互关联的状态有复杂的状态修改逻辑 持久化需求 需要将状态保存到localStorage/sessionStorage页面刷新后需要恢复状态&#xff08;恢复最后一次修改的状态&#xff0…...

学习STC51单片机25(芯片为STC89C52RCRC)

每日一言 生活就像弹簧&#xff0c;你弱它就强&#xff0c;你强它就弱&#xff0c;别轻易认输。 ESP8266作为路由器模式&#xff08;AP模式&#xff09;也就是在局域网内可以有服务器的作用 那么我们需要将pc作为设备进行连接ESP的发射出来的WIFE 叫做这个AI啥的 也有可能叫做…...

宁夏农业科技:创新引领,赋能现代农业新篇章

在广袤的宁夏大地上&#xff0c;农业科技如同一股强劲的春风&#xff0c;吹拂着每一寸土地&#xff0c;为宁夏的农业发展注入了新的活力与希望。近年来&#xff0c;宁夏农业科技以其独特的创新力和实践力&#xff0c;不断推动着现代农业的转型升级&#xff0c;让这片古老的土地…...

Accelerate 2025北亚巡展正式启航!AI智御全球·引领安全新时代

近日&#xff0c;网络安全行业年度盛会Accelerate 2025北亚巡展正式在深圳启航&#xff01;智库专家、产业领袖及Fortinet高管、产品技术团队和300余位行业客户齐聚一堂&#xff0c;围绕“AI智御全球引领安全新时代”主题&#xff0c;共同探讨AI时代网络安全新范式。大会聚焦三…...

005学生心理咨询评估系统技术解析:搭建科学心理评估平台

学生心理咨询评估系统技术解析&#xff1a;搭建科学心理评估平台 在心理健康教育日益受重视的当下&#xff0c;学生心理咨询评估系统成为了解学生心理状态的重要工具。该系统涵盖试卷管理、试题管理等核心模块&#xff0c;面向管理员和用户两类角色&#xff0c;通过前台展示与…...

azure devops 系列 - 常用的task

任务在管道中执行操作。例如,任务可以构建应用、与 Azure 资源交互、安装工具或运行测试。任务是定义管道中自动化的构建基块。 运行作业时,所有任务都会按顺序依次运行。要在多个代理上并行运行同一组任务,或者在不使用代理的情况下运行某些任务,使用job。 Build Task …...

贪心算法应用:多重背包启发式问题详解

贪心算法应用&#xff1a;多重背包启发式问题详解 多重背包问题是经典的组合优化问题&#xff0c;也是贪心算法的重要应用场景。本文将全面深入地探讨Java中如何利用贪心算法解决多重背包问题。 多重背包问题定义 **多重背包问题(Multiple Knapsack Problem)**是背包问题的变…...

【保姆级教程】PDF批量转图文笔记

如果你有一个PDF文档&#xff0c;然后你想把它发成图文笔记emmm&#xff0c;最好再加个水印&#xff0c;你会怎么做&#xff1f; 其实也不麻烦&#xff0c;打开PDF文档&#xff0c;挨个截图&#xff0c;然后打开PS一张一张图片拖进去&#xff0c;再把水印图片拖进去&#xff0…...

Pytest Fixture 是什么?

Fixture 是什么&#xff1f; Fixture 是 Pytest 测试框架的核心功能之一&#xff0c;用于为测试函数提供所需的依赖资源或环境。它的核心目标是&#xff1a; ✅ 提供测试数据&#xff08;如模拟对象、数据库记录&#xff09; ✅ 初始化系统状态&#xff08;如配置、临时文件&a…...

Spring Boot 基础知识全面解析:快速构建企业级应用的核心指南

一、Spring Boot 概述&#xff1a;重新定义 Java 开发 1.1 什么是 Spring Boot&#xff1f; Spring Boot 是基于 Spring 框架的快速开发框架&#xff0c;旨在简化 Spring 应用的初始搭建及开发过程。它通过 「约定优于配置」&#xff08;Convention Over Configuration&#…...

数据库系统概论(十一)SQL 集合查询 超详细讲解(附带例题表格对比带你一步步掌握)

数据库系统概论&#xff08;十一&#xff09;SQL 集合查询 超详细讲解&#xff08;附带例题表格对比带你一步步掌握&#xff09; 前言一、什么是集合查询&#xff1f;二、集合操作的三种类型1. 并操作2. 交操作3. 差操作 三、使用集合查询的前提条件四、常见问题与注意事项五、…...

[mcu]系统频率

系统主频的选择直接影响性能、功耗和成本&#xff0c;不同厂商的芯片会根据应用场景设计不同的运行频率。 低频段80MHZ~160MHz 典型频率&#xff1a; 80MHz、120MHz、160MHz 特点&#xff1a; 低功耗&#xff0c;适合电池供电设备 处理能力有限&#xff0c;通常仅支持 单天线…...

clickhouse如何查看操作记录,从日志来查看写入是否成功

背景 插入表数据后&#xff0c;因为原本表中就有数据&#xff0c;一时间没想到怎么查看插入是否成功&#xff0c;因为对数据源没有很多的了解&#xff0c;这时候就想怎么查看下插入是否成功呢&#xff0c;于是就有了以下方法 具体方法 根据操作类型查找&#xff0c;比如inse…...

5G-A:开启通信与行业变革的新时代

最近&#xff0c;不少细心的用户发现手机信号标识悄然发生了变化&#xff0c;从熟悉的 “5G” 变成了 “5G-A”。这一小小的改变&#xff0c;却蕴含着通信技术领域的重大升级&#xff0c;预示着一个全新的通信时代正在向我们走来。今天&#xff0c;就让我们深入了解一下 5G-A&a…...

鸿蒙OS在UniApp中集成Three.js:打造跨平台3D可视化应用#三方框架 #Uniapp

在UniApp中集成Three.js&#xff1a;打造跨平台3D可视化应用 引言 在最近的一个项目中&#xff0c;我们需要在UniApp应用中展示3D模型&#xff0c;并实现实时交互功能。经过技术选型和实践&#xff0c;我们选择了Three.js作为3D渲染引擎。本文将分享我们在UniApp中集成Three.…...

Vue 3 组件化设计实践:构建可扩展、高内聚的前端体系

Vue 3 自发布以来&#xff0c;其引入的 Composition API 与改进的组件模型&#xff0c;为前端架构提供了更强的可组合性、复用性与模块化能力。本文将系统性探讨 Vue 3 如何通过组件化设计&#xff0c;实现复杂应用的解耦、扩展与维护&#xff0c;并结合实际工程经验提供最佳实…...

腾讯云 Python3.12.8 通过yum安装 并设置为默认版本

在腾讯云服务器上&#xff0c;直接通过 yum 安装 Python 3.12.8 可能不可行&#xff0c;因为标准仓库通常不包含最新的 Python 版本。不过&#xff0c;我们可以通过添加第三方仓库或手动安装 RPM 包的方式实现。以下是完整解决方案&#xff1a; 方法 1: 通过第三方仓库安装&am…...

鸿蒙OSUniApp页面切换动效实战:打造流畅精致的转场体验#三方框架 #Uniapp

UniApp页面切换动效实战&#xff1a;打造流畅精致的转场体验 引言 在移动应用开发中&#xff0c;页面切换动效不仅能提升用户体验&#xff0c;还能传达应用的品质感。随着HarmonyOS的普及&#xff0c;用户对应用的动效体验要求越来越高。本文将深入探讨如何在UniApp中实现流畅…...

React 泛型组件:用TS来打造灵活的组件。

文章目录 前言一、什么是泛型组件&#xff1f;二、为什么需要泛型组件&#xff1f;三、如何在 React 中定义泛型组件&#xff1f;基础泛型组件示例使用泛型组件 四、泛型组件的高级用法带默认类型的泛型组件多个泛型参数 五、泛型组件的实际应用场景数据展示组件表单组件状态管…...

TDengine 集群运行监控

简介 为了确保集群稳定运行&#xff0c;TDengine 集成了多种监控指标收集机制&#xff0c;并通过 taosKeeper 进行汇总。taosKeeper 负责接收这些数据&#xff0c;并将其写入一个独立的 TDengine 实例中&#xff0c;该实例可以与被监控的 TDengine 集群保持独立。TDengine 中的…...

图像任务中的并发处理:线程池、Ray、Celery 和 asyncio 的比较

在图像缺陷检测任务中&#xff0c;处理大量图像和点云数据时&#xff0c;高效的并发处理是关键。本文将介绍五种流行的并发处理方法&#xff1a;线程池&#xff08;concurrent.futures.ThreadPoolExecutor&#xff09;、Ray、Celery、asyncio以及搜狗Workflow&#xff0c;并从原…...

DeepSeek 赋能智能物流:解锁仓储机器人调度的无限可能

目录 一、智能物流仓储机器人调度现状1.1 传统调度面临的挑战1.2 现有智能调度的进展与局限 二、DeepSeek 技术探秘2.1 DeepSeek 核心技术原理2.2 DeepSeek 的独特优势 三、DeepSeek 在智能物流仓储机器人调度中的创新应用3.1 智能任务分配与调度3.2 路径规划与避障优化3.3 实时…...

C#上传图片后压缩

上传的图片尺寸不一&#xff0c;手机拍照的有2000*2000像素的&#xff0c;对实际使用来说 文件尺寸太大&#xff0c;文件也有近4M 下面是直接压缩的方法 1、安装包 Magick.NET-Q16-AnyCPU 2、上代码 /// <summary> /// 缩放图片 /// </summary> /// <param …...

uniapp路由跳转toolbar页面

需要阅读uview-ui的API文档 注意需要使用type参数设置后才起作用 另外route跳转的页面会覆盖toolbar工具栏 toConternt(aid) {console.log(aid:, aid)this.$u.route({// url: "pages/yzpg/detail",url: "pages/yzappl/index",// url: "pages/ind…...

【linux】知识梳理

操作系统的分类 1. 桌⾯操作系统: Windows/macOS/Linux 2. 移动端操作系统: Android(安卓)/iOS(苹果) 3. 服务器操作系统: Linux/Windows Server 4. 嵌⼊式操作系统: Android(底层是 Linux) Liunx介绍 liunx系统:服务器端最常见的操作系统类型 发行版:Centos和Ubuntu 远程连接操…...

PostgreSQL 内置扩展列表

PostgreSQL 内置扩展列表 PostgreSQL 自带了许多内置扩展&#xff08;built-in extensions&#xff09;&#xff0c;这些扩展提供了额外的功能而不需要额外安装。以下是主要的内置扩展分类和说明&#xff1a; 标准内置扩展&#xff08;随核心安装&#xff09; 1. 管理类扩展…...

NodeMediaEdge快速上手

NodeMediaEdge快速上手 简介 NodeMediaEdge是一款部署在监控摄像机网络前端中&#xff0c;拉取Onvif或者rtsp/rtmp/http视频流并使用rtmp/kmp推送到公网流媒体服务器的工具。 通过云平台协议注册到NodeMediaServer后&#xff0c;可以同NodeMediaServer结合使用。使用图形化的…...