数据结构:递归的种类(Types of Recursion)
目录
尾递归(Tail Recursion)
什么是 Loop(循环)?
复杂度分析
头递归(Head Recursion)
树形递归(Tree Recursion)
线性递归(Linear Recursion)
递归调用树(fun(3))
栈帧变化
复杂度分析
间接递归(Indirect Recursion)
复杂度分析
嵌套递归(Nested Recursion)
递归调用树
尾递归(Tail Recursion)
尾递归是递归函数的一种特殊形式:
一个函数在递归调用之后,不再做任何额外的操作,而是“直接返回”这个递归调用的结果。
也就是说,递归调用是函数中的最后一步,没有其他计算或操作跟在它后面。
void fun(int x)
{if(x > 0){printf("%d", x);fun(x - 1);}
}
分析:
-
printf("%d", x);
是当前函数的最后一个非递归操作。 -
fun(x - 1);
是函数体中最后一个执行的动作,它后面没有其它操作。 -
调用的返回值也没有被用在任何表达式中,比如赋值、加法等。
✅所以这是尾递归。
在尾递归中,当前函数的调用栈帧不需要保存,因为没有额外操作依赖返回值,编译器甚至可以优化为循环(loop),提高效率。
void fun(int n)
{if(n > 0){printf("%d", n);fun(n - 1) + n;}
}
分析:
-
这里虽然
fun(n - 1)
是递归调用,但它不是函数体中的最后操作。 -
还有一个
+ n
—— 表示你需要获取fun(n - 1)
的返回值后,再与n
相加。 -
这意味着 当前栈帧必须保留,等待子调用返回后进行加法操作。
❌ 所以这不是尾递归。
什么是 Loop(循环)?
Loop(循环) 是程序中一种重复执行代码的结构。
常见的 C++ 循环有:
-
for
循环:已知循环次数 -
while
循环:满足条件就循环 -
do...while
:至少执行一次
将递归转化为 循环
void fun_loop(int x)
{while(x > 0){printf("%d", x);x--;}
}
转化步骤说明:
-
把
x
看作循环变量; -
fun(x - 1);
→x--
; -
把
if (x > 0)
→ 变成while(x > 0)
; -
保持
printf
逻辑不变;
🎯 核心联系:
-
所有尾递归都可以被转换为循环(Loop);
-
但非尾递归不一定能直接转换为 Loop(可能需要用栈模拟)。
复杂度分析
时间复杂度分析
无论是递归版本还是循环版本,printf
一次就是常数时间 O(1)
,每次都打印一个数字。
递归版本:
-
当
x = n
时,会调用fun(n), fun(n-1), ..., fun(1)
,共n
次。 -
每次调用只做一次
printf
,没有重复计算。
✅ 时间复杂度:O(n)
循环版本:
同样执行 n
次 printf
,逻辑完全等价于递归。
✅ 时间复杂度:O(n)
空间复杂度分析
递归版本:
-
每一次函数调用,都会占用一层调用栈帧。
-
所以
fun(5)
会占用 5 层栈空间。 -
栈大小是受语言/平台限制的,过多递归可能导致 栈溢出(stack overflow)。
❌ 空间复杂度:O(n)(用于递归栈)
循环版本:
-
不存在函数嵌套调用,变量
x
是一个普通变量。 -
不会占用额外栈帧。
✅ 空间复杂度:O(1)(常量空间)
头递归(Head Recursion)
头递归(Head Recursion) 是指:
函数中 先调用自己,再执行其他操作 的递归形式。
💡 特点:
-
函数体中,递归调用在最前面。
-
所有的动作都发生在“返回之后”。
-
结构上与“尾递归”相反,先深入到底,再回溯处理”。
void fun(int n)
{if(n > 0){fun(n - 1);printf("%d", n);}
}
调用流程:
fun(3)
└── fun(2)└── fun(1)└── fun(0) → 终止
然后开始回溯:
-
尾递归:打印从 3 → 2 → 1(调用时就执行)
-
头递归:打印从 1 → 2 → 3(回溯时执行)
将头递归转换为循环
void fun(int n)
{int i = 1;while(i<=n){printf("%d",i);i++;}
}
树形递归(Tree Recursion)
树形递归是指函数在每次调用时,会递归地调用自己 两次或更多次,从而形成一个“树状”结构的调用图。
📌 特征:
-
每个递归分支会继续递归产生更多子分支。
-
递归调用数量呈指数级增长。
-
典型例子是斐波那契数列、全排列、子集生成、分治算法等。
线性递归(Linear Recursion)
线性递归是指函数在每次调用时,只进行一次递归调用,然后再进行其他操作。
📌 特征:
-
递归过程是一条“直线”,只有一条路径向下。
-
每一层只调用下一层一次。
-
常见于计数、求和、遍历等线性结构。
void fun(int n)
{if(n > 0){printf("%d", n);fun(n - 1);fun(n - 1);}
}
递归调用树(fun(3))
fun(3)/ \printf(3) printf(3)fun(2) fun(2)/ \ / \printf(2) printf(2) printf(2) printf(2)fun(1) fun(1) fun(1) fun(1)/ \ / \ / \ / \printf(1) printf(1) ... ... ... ... ... ...fun(0) fun(0) fun(0) fun(0) fun(0) fun(0) fun(0) fun(0)
fun(3)/ \fun(2) fun(2)/ \ / \fun(1) fun(1) fun(1) fun(1)/ \ / \ / \ / \fun(0) fun(0) fun(0) fun(0) fun(0) fun(0) fun(0) fun(0)每个 fun(n) 节点都对应一次:printf(n)
栈帧变化
我们使用“压栈(调用)”和“弹栈(返回)”来标记栈帧变化。
⬇️ 调用开始:main()
→ fun(3)
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
打印:3
⬇️ fun(3)
调用 fun(2)
:
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
打印:2
⬇️ fun(2)
调用 fun(1)
:
+------------------------+
| fun(n=1) 的栈帧 |
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
打印:1
⬇️ fun(1)
调用 fun(0)
+------------------------+
| fun(n=0) 的栈帧 |
+------------------------+
| fun(n=1) 的栈帧 |
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
n == 0
,返回,弹出 fun(0) 栈帧:
+------------------------+
| fun(n=1) 的栈帧 |
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
⬇️ fun(1)
再调用第二个 fun(0)
+------------------------+
| fun(n=0) 的栈帧 |
+------------------------+
| fun(n=1) 的栈帧 |
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
返回,弹出 fun(0):
+------------------------+
| fun(n=1) 的栈帧 |
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
fun(1)
完成,弹出:
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
⬇️ fun(2)
现在调用第二个 fun(1)
(结构一样)
⬇️ fun(3)
现在调用第二个 fun(2)
调用 fun(2) → fun(1) → fun(0) → fun(0) → 返回
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
进入 fun(1)
,打印 1
,两次 fun(0),弹栈
再次 fun(1)
,打印 1
,两次 fun(0),弹栈
完成所有调用,弹出所有栈帧。
最终输出顺序:3 2 1 1 2 1 1
复杂度分析
时间复杂度分析:递归调用树逐层计数
调用树节点数量(按层)
层数(Level) | 调用的是 fun(x) | 节点个数 |
---|---|---|
第 0 层 | fun(3) | 1 |
第 1 层 | fun(2) | 2 |
第 2 层 | fun(1) | 4 |
第 3 层 | fun(0) | 8(终止) |
总调用次数 = 每个 fun(n)
的数量 = 1 + 2 + 4 + 8 = 15
这是一个 等比数列求和:
当 n = 3
时:
T(3)=2^4−1=15
当 n 变大时,忽略常数与低阶项:时间复杂度:T(n) = O(2^n)
空间复杂度分析:根据栈深度(调用路径)分析
空间复杂度 = 递归过程中 最大栈帧深度
一个分支调用路径是:
fun(3)→ fun(2)→ fun(1)→ fun(0)
-
每次递归调用先执行第一个子调用,然后第二个
-
每个调用都等第二个调用结束后才能返回
-
所以是深度优先的栈式调用
最大栈帧数 = 调用最长的一条路径
+------------------------+
| fun(n=0) 的栈帧 |
+------------------------+
| fun(n=1) 的栈帧 |
+------------------------+
| fun(n=2) 的栈帧 |
+------------------------+
| fun(n=3) 的栈帧 |
+------------------------+
| main() 的栈帧 |
+------------------------+
共计 4 层栈帧
空间复杂度结论:
空间复杂度 = 最大栈帧数 = O(n)
(这里 n
是初始的 fun(n)
的值)
间接递归(Indirect Recursion)
间接递归是指:函数 A 调用函数 B,函数 B 又调用函数 A,形成“循环调用”结构,但自己不直接调用自己。
📌 特征:
-
A → B → A → B → … 互相递归
-
没有函数直接调用自己,但仍然形成递归链
-
本质上仍然需要使用函数栈,类似直接递归的行为
void funA(int n)
{if(n > 0){printf("%d", n);funB(n - 1);}
}void funB(int x)
{if(x > 0){printf("%d", x);funA(x / 2);}
}
递归调用树
funA(3)/ \printf(3) funB(2)/ \printf(2) funA(1)/ \printf(1) funB(0)
复杂度分析
✅时间复杂度
是否能泛化为 n 的函数?
我们发现:
-
每次 funA(n) → funB(n-1)
-
每次 funB(x) → funA(x/2)
所以这是一个交错型链式调用。
建函数调用深度模型(从 n=3 推到 n)
设:
-
A 调用 B(n-1)
-
B 调用 A(n/2)
构成链状结构:
A(n) → B(n-1) → A((n-1)/2) → B((n-1)/2 - 1) → ...
n → n-1 → ⌊(n-1)/2⌋ → ⌊(n-1)/2⌋ - 1 → ...
这是一个交替递减+对数递减的组合过程。
下降几次会结束?
这是一个类似:
n→n−1→⌊2n−1⌋→⌊2n−1⌋−1→...
混合了 线性减1 和 对数除2 的过程
最多不会超过:
-
线性下降:O(n)
-
对数下降:O(log n)
所以 时间复杂度:O(log n + n) = O(n)(近似线性)
✅空间复杂度(最大栈帧)
因为调用是链状交替(A→B→A→B...),所以 最大调用深度 就是这条链的长度。
最多有 O(n) 次连续调用 → 最大栈深度为 O(n)
嵌套递归(Nested Recursion)
嵌套递归是指:递归调用的参数本身又是一个递归调用。
也就是:
fun(fun(n + 11))
它的核心区别是:
-
递归调用不是
fun(n - 1)
这种简单线性变化; -
而是 “求出 fun(n+11) 的结果,然后再用它作为参数再次调用
fun
”。
int fun(int n)
{if(n > 100){return n - 10;}else{return fun(fun(n + 11));}
}
递归调用树
fun(95)/ \fun(106) = 96 fun(96)/ \fun(107) = 97 fun(97)/ \fun(108) = 98 fun(98)/ \fun(109) = 99 fun(99)
fun(99)/ \fun(110) = 100 fun(100)/ \fun(111) = 101 fun(101)↓base case
最终回溯后 fun(95)
会返回 91
相关文章:

数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...

保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...

沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...

Sklearn 机器学习 缺失值处理 获取填充失值的统计值
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 使用 Scikit-learn 处理缺失值并提取填充统计信息的完整指南 在机器学习项目中,数据清…...

FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...

从物理机到云原生:全面解析计算虚拟化技术的演进与应用
前言:我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM(Java Virtual Machine)让"一次编写,到处运行"成为可能。这个软件层面的虚拟化让我着迷,但直到后来接触VMware和Doc…...

五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...

GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL
ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...

Axure 下拉框联动
实现选省、选完省之后选对应省份下的市区...

【UE5 C++】通过文件对话框获取选择文件的路径
目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...
DiscuzX3.5发帖json api
参考文章:PHP实现独立Discuz站外发帖(直连操作数据库)_discuz 发帖api-CSDN博客 简单改造了一下,适配我自己的需求 有一个站点存在多个采集站,我想通过主站拿标题,采集站拿内容 使用到的sql如下 CREATE TABLE pre_forum_post_…...

第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)
第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10pip3.10) 一:前言二:安装编译依赖二:安装Python3.10三:安装PIP3.10四:安装Paddlepaddle基础框架4.1…...

自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...

高考志愿填报管理系统---开发介绍
高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发,采用现代化的Web技术,为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## 📋 系统概述 ### 🎯 系统定…...