完整教程:数据结构:递归的种类(Types of Recursion)

news/2025/10/7 8:15:11/文章来源:https://www.cnblogs.com/ljbguanli/p/19128164

完整教程:数据结构:递归的种类(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--;
}
}

转化步骤说明:

  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.mzph.cn/news/930196.shtml

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

相关文章

专业模板网站制作wordpress apple pro

当代年轻人的生活方式是怎样的&#xff1f;靠地铁通勤&#xff0c;靠咖啡续命早上睁不开眼&#xff0c;咖啡来一杯中午昏昏欲睡&#xff0c;咖啡来一杯晚上熬夜加班&#xff0c;咖啡来一杯喝完这杯&#xff0c;还有一杯“宁可食无肉&#xff0c;不可早无星”是当代年轻人的座右…

网站建设首选wordpress首页不显示指定分类

1、对比 tairzookeper性能高 低可靠性低 高 2、zookeper实现分布式锁 特点&#xff1a; Zookeeper能保证数据的强一致性&#xff0c;用户任何时候都可以相信集群中每个节点的数据都是相同的。 加锁 客户端在ZooKeeper一个特定的节点下创建临时顺序节点&…

Nova Premier模型安全评估结果解析

本文通过第三方评估机构对Nova Premier模型进行黑盒压力测试和红队演练,展示了该模型在安全防护方面的卓越表现,包括在恶意指令抵抗和有害内容生成防护方面的技术细节。独立评估证明 Nova Premier 的安全性 - 某中心…

改写自己的浏览器插件工具 myChromeTools - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

做移动网站优化排名网站建设需要编码不

基本数据类型 Java 基础数据按类型可以分为四大类&#xff1a;布尔型、整数型、浮点型、字符型&#xff0c;这四大类包含 8 种基础数据类型。 布尔型&#xff1a;boolean整数型&#xff1a;byte、short、int、long浮点型&#xff1a;float、double字符型&#xff1a;char 八种…

网站设计需要那些人绍兴seo推广

hash 模式 【推荐】 路由效果 在不刷新页面的前提下&#xff0c;根据 URL 中的 hash 值&#xff0c;渲染对应的页面 http://test.com/#/login 登录页http://test.com/#/index 首页 核心API – window.onhashchange 监听 hash 的变化&#xff0c;触发视图更新 window.onhas…

通过litestream 进行sqlite-vec 数据备份以及恢复

通过litestream 进行sqlite-vec 数据备份以及恢复实际上就是一个简单的测试,litestream 支持流式复制,比较适合对于sqlite进行备份,同时litestream 还支持对象存储的remote 模式,比较方便 环境准备minioservices: …

相册网站开发那个网站开发三味

强制类型转换形式&#xff1a;(类型说明符) (表达式)举例说明&#xff1a;1) int a;a (int)1.9;2)char *b;int *p;p (int *) b; //将b的值强制转换为指向整型数据的指针类型&#xff0c;后赋给p注示&#xff1a;类型说明符和表达式都必须加括号&#xff0c;表达式为单个变量可…

做购物网站适合的服务器网站建设需求模版

目录 前言 一、创建上下文类 1.自定义MyContext上下文类继承IdentityDbContext 2.在Program中添加AddDbContext服务 二、使用Migration数据迁移 1.在控制台中 依次使用add-migration 、updatebase 命令 2.如何修改表名 3.如何自定义字段 三、使用Identity实现登录、修改密码 …

对于路由使用的ref的疑问

<script setup>import { ref, computed } from vueimport Home from ./Home.vueimport About from ./About.vueimport NotFound from ./NotFound.vueconst routes = { /: Home, /about: About}const currentPa…

天津到天津天津网站开发iis v6 新建网站

大数据管理数据处理过程图大数据(big data),指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察力。大数据处理的主要流程包括数据收集、数据存储、数据处理、数据应用等主要环节。随着业务的增长,大量和流程、规…

自建购物网站福建响应式网站制作

目录 详细布置&#xff1a; 1. 层序遍历 2. 226. 翻转二叉树 3. 101. 对称二叉树 详细布置&#xff1a; 1. 层序遍历 昨天练习了几种二叉树的深度优先遍历&#xff0c;包括&#xff1a; ​​​​​​前中后序的递归法前中后序的迭代法前中后序迭代的统一写法 今天&…

扁平化企业网站从零开始制作wordpress主题

文章目录 360篡改浏览器主页方法1锁定浏览器主页 方法2注册表修改 360广告和弹窗360极速版 小结 360篡改浏览器主页 如果您使用360,且不想卸载它,那么当你启动360后,它可能会篡改你的浏览器(比如edge)的主页start page为360早期可能是通过修改快捷方式的target等属性,但是现在…

新网站建设验收公司手机网站模板

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…

公司专业网站建设百度分享代码 wordpress

背景&#xff1a; 想知道四川省包含哪些水系&#xff0c;以及各个水系的分布&#xff0c;起点、流经省市、终点等 {label: "嘉陵江",value: "嘉陵江",},{label: "渠江",value: "渠江",},{label: "涪江",value: "涪江&q…

Paypal 设置不自动换汇

进入 PayPal 网页版自动付款设置。选择结算商户,查看兑换选项,修改为使用银行的兑换方式。

网站建设培训赚钱吗怎么才能在百度上做引流呢

目录 引出Redis持久化方式Redis入门1.Redis是什么&#xff1f;2.Redis里面存Java对象 Redis进阶1.雪崩/ 击穿 / 穿透2.Redis高可用-主从哨兵3.持久化RDB和AOF4.Redis未授权访问漏洞5.Redis里面安装BloomFilte Redis的应用1.验证码2.Redis高并发抢购3.缓存预热用户注册验证码4.R…

威县网站建设报价微信app开发价格表

我们需要将Python对象序列化为字节流&#xff0c;这样就可以将其保存到文件中、存储到数据库中或者通过网络连接进行传输。 解决方案 序列化最普遍的做法是使用 pickle 模块。为了将一个对象保存到一个文件中&#xff0c;可以这样做&#xff1a; import pickledata ... # Some…

国外网站参考住房城乡与建设厅网站首页

压缩方式是网络视频服务器和网络摄像机的核心技术&#xff0c;压缩方式很大程度上决定着图像的质量、压缩比、传输效率、传输速度等性能&#xff0c;它是评价网络视频服务器和网络摄像机性能优劣的重要一环。 随着多媒体技术的发展&#xff0c;相继推出了许多压缩编码标准&…

中博建设集团有限公司网站湖南软件开发公司

前言 由于兼容性问题&#xff0c;使得我们若想用较新版本的 PyTorch&#xff0c;通过 GPU 方式训练模型&#xff0c;也得更换较新版本得 CUDA 工具包。然而 CUDA 的版本又与电脑显卡的驱动程序版本关联&#xff0c;如果是低版本的显卡驱动程序安装 CUDA11 及以上肯定会失败。 比…