数据结构:递归的种类(Types of Recursion)

news2025/6/10 18:46:37

目录

尾递归(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--;
	}
}

转化步骤说明:

  1. x 看作循环变量;

  2. fun(x - 1);x--;

  3. if (x > 0) → 变成 while(x > 0);

  4. 保持 printf 逻辑不变;

🎯 核心联系:

  • 所有尾递归都可以被转换为循环(Loop);

  • 但非尾递归不一定能直接转换为 Loop(可能需要用栈模拟)。

 复杂度分析

时间复杂度分析

无论是递归版本还是循环版本,printf 一次就是常数时间 O(1),每次都打印一个数字。

递归版本:

  • x = n 时,会调用 fun(n), fun(n-1), ..., fun(1),共 n 次。

  • 每次调用只做一次 printf,没有重复计算。

✅ 时间复杂度:O(n)

 循环版本:

同样执行 nprintf,逻辑完全等价于递归。

✅ 时间复杂度: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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2406940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保姆级【快数学会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.效果展示 逐帧…

沙箱虚拟化技术虚拟机容器之间的关系详解

问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西&#xff0c;但是如果把三者放在一起&#xff0c;它们之间到底什么关系&#xff1f;又有什么联系呢&#xff1f;我不是很明白&#xff01;&#xff01;&#xff01; 就比如说&#xff1a; 沙箱&#…

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

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

VisualXML全新升级 | 新增数据库编辑功能

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

Sklearn 机器学习 缺失值处理 获取填充失值的统计值

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

FFmpeg avformat_open_input函数分析

函数内部的总体流程如下&#xff1a; avformat_open_input 精简后的代码如下&#xff1a; int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)

目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 ​编辑​编辑 UDP的特征 socke函数 bind函数 recvfrom函数&#xff08;接收函数&#xff09; sendto函数&#xff08;发送函数&#xff09; 五、网络编程之 UDP 用…

从物理机到云原生:全面解析计算虚拟化技术的演进与应用

前言&#xff1a;我的虚拟化技术探索之旅 我最早接触"虚拟机"的概念是从Java开始的——JVM&#xff08;Java Virtual Machine&#xff09;让"一次编写&#xff0c;到处运行"成为可能。这个软件层面的虚拟化让我着迷&#xff0c;但直到后来接触VMware和Doc…

五子棋测试用例

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

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…

Axure 下拉框联动

实现选省、选完省之后选对应省份下的市区

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…

热烈祝贺埃文科技正式加入可信数据空间发展联盟

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

rknn toolkit2搭建和推理

安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 &#xff0c;不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源&#xff08;最常用&#xff09; conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…

ZYNQ学习记录FPGA(一)ZYNQ简介

一、知识准备 1.一些术语,缩写和概念&#xff1a; 1&#xff09;ZYNQ全称&#xff1a;ZYNQ7000 All Pgrammable SoC 2&#xff09;SoC:system on chips(片上系统)&#xff0c;对比集成电路的SoB&#xff08;system on board&#xff09; 3&#xff09;ARM&#xff1a;处理器…

第一篇:Liunx环境下搭建PaddlePaddle 3.0基础环境(Liunx Centos8.5安装Python3.10+pip3.10)

第一篇&#xff1a;Liunx环境下搭建PaddlePaddle 3.0基础环境&#xff08;Liunx Centos8.5安装Python3.10pip3.10&#xff09; 一&#xff1a;前言二&#xff1a;安装编译依赖二&#xff1a;安装Python3.10三&#xff1a;安装PIP3.10四&#xff1a;安装Paddlepaddle基础框架4.1…

自然语言处理——文本分类

文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益&#xff08;IG&#xff09; 分类器设计贝叶斯理论&#xff1a;线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别&#xff0c; 有单标签多类别文本分类和多…