对递归两层含义的理解

news/2025/11/3 19:11:00/文章来源:https://www.cnblogs.com/mysticbinary/p/19174214

目录
  • 背景知识
  • Definition
    • 从堆栈的角度理解递归
    • 缺点
  • 和For循环的区别
  • 总结
  • Reference


背景知识

先了解一下内存结构,但这个不是必须的。
image


Definition

递归是一个循环结构,主要用来处理需要循环执行的任务,和For循环类似的代码结构。
简单说就是函数自己能调用自己。

fun factorial(n:Int):Int{if(n <= 1) return nelse return n*factorial(n-1)
}

用图理解:
image

image

从堆栈的角度理解递归

递归的底层就是利用堆栈和指令结构所实现的功能。

image

缺点

从使用堆栈的资源角度来说,会比For以及其他循环结构更耗资源。

  1. 内存开销
  • 堆栈:每次函数调用都会在栈上分配新的栈帧,包含参数、返回地址、局部变量等
  • 循环:只在当前栈帧内重复执行,不需要额外的栈空间
  1. 上下文切换成本
  • 堆栈(递归):涉及函数调用、返回地址保存、寄存器保存等操作
  • 循环:简单的跳转指令,几乎没有上下文切换开销
  1. 缓存效率
  • 堆栈:频繁的函数调用可能导致缓存失效
  • 循环:代码局部性更好,缓存命中率更高

和For循环的区别

和For循环的区别有很多,但我主要想讨论他们对解决问题的思考方式上的差别。这个也是我想让大家理解的第二层含义。

如果说递归的第一层含义是:自己调用自己。
那么递归的第二层含义是:上一次的函数输出,可以成为下一次函数的输入。

案例——阶梯问题:
我们以爬楼梯问题为例:有n阶台阶,每次可以爬1或2阶,问有多少种不同的方法爬到楼顶?

这实际上就是求斐波那契数列。

递归终止条件:
当 n=1 时,只有1种方法:爬1阶。
当 n=2 时,有两种方法:一次爬2阶,或者两次各爬1阶。

对于n>2的情况,考虑第一步有两种选择:
第一步爬1阶,那么剩下的n-1阶台阶有 climb_stairs_recursive(n-1) 种方法。
第一步爬2阶,那么剩下的n-2阶台阶有 climb_stairs_recursive(n-2) 种方法。

因此,爬n阶台阶的方法数等于这两种情况的方法数之和,即:
climb_stairs_recursive(n) = climb_stairs_recursive(n-1) + climb_stairs_recursive(n-2)

举个例子:n=3
第一步爬1阶,剩下2阶:有2种方法(爬2阶:一次2阶,或两次1阶)
第一步爬2阶,剩下1阶:有1种方法(爬1阶)
所以总共2+1=3种方法。

同理,n=4:
第一步爬1阶,剩下3阶:3种方法(上面n=3的情况)
第一步爬2阶,剩下2阶:2种方法(n=2的情况)
所以总共3+2=5种。

想要知道4阶有多少种,那么需要先知道3阶有多少种?
那么,想知道3阶有多少种,那么就得知道2阶有多少种?
一直追问到终止条件为止。

def climb_stairs_recursive(n):# 基础情况if n == 1:return 1  # 只有1种方法:走1步if n == 2:return 2  # 2种方法:1→1 或 2# 递归关系:f(n) = f(n-1) + f(n-2)return climb_stairs_recursive(n-1) + climb_stairs_recursive(n-2)

为了方便理解,加上了打印参数:

def climb_stairs_recursive(n, depth=0):indent = "  " * depth  # 根据深度缩进,显示调用层次print(f"{indent}-> climb_stairs({n})")# 基础情况if n == 1:print(f"{indent}<- 返回 1 (基础情况: n=1)")return 1if n == 2:print(f"{indent}<- 返回 2 (基础情况: n=2)")return 2# 递归调用print(f"{indent}  计算 climb_stairs({n - 1}) + climb_stairs({n - 2})")left = climb_stairs_recursive(n - 1, depth + 1)right = climb_stairs_recursive(n - 2, depth + 1)result = left + rightprint(f"{indent}<- 返回 {result} = {left} + {right}")return resultprint("计算 climb_stairs(4):")
print(f"最终结果: {climb_stairs_recursive(4)}")#print:
计算 climb_stairs(4):
-> climb_stairs(4)计算 climb_stairs(3) + climb_stairs(2)-> climb_stairs(3)计算 climb_stairs(2) + climb_stairs(1)-> climb_stairs(2)<- 返回 2 (基础情况: n=2)-> climb_stairs(1)<- 返回 1 (基础情况: n=1)<- 返回 3 = 2 + 1-> climb_stairs(2)<- 返回 2 (基础情况: n=2)
<- 返回 5 = 3 + 2
最终结果: 5

对比for循环的写法:

# 需要在写代码的时候,自己维护状态
def climb_stairs_iterative(n):if n <= 2:return na, b = 1, 2for i in range(3, n+1):a, b = b, a + breturn b

在台阶问题(比如爬楼梯,每次可以走1步或2步,问n阶台阶有多少种走法)中,递归和循环都可以实现,但各有优劣。

递归方式的优点:

  • 代码直观,易于理解:递归往往能够直接反映问题的数学定义,比如斐波那契数列、爬楼梯问题的递推关系。
  • 易于设计和实现:对于复杂问题,递归可以简化设计过程。

但是,在台阶问题中,递归的缺点也很明显:

  • 效率低:存在大量重复计算,导致指数级的时间复杂度。
  • 栈溢出风险:当n较大时,递归深度过深,会导致栈溢出。
  • 而循环(动态规划)方式则通过迭代和保存中间结果,避免了重复计算,时间复杂度为O(n),空间复杂度可以优化到O(1)。

总结

在快速原型阶段,可以使用递归是实现算法,好处是:

  • 快速验证算法思路
  • 更容易修改和调整逻辑

方便快速实现。

而到了中后期的优化阶段,可以考虑把循环结构改成动态规划(for)循环,以节省内存资源。

还有,
递归的直观性和易于理解是其主要优点,但在性能上不如循环。如果一个问题在初期就很容易用循环想清楚,则没必要使用递归。


Reference

《秒懂算法》 作者:杰伊·温格罗

https://zh.javascript.info/recursion

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

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

相关文章

怎么在现有App里融入AI对话能力

怎么在现有App里融入AI对话能力在数字化交互日益重要的今天,用户对App的智能化期望越来越高。尤其是在社交、电商、客服等领域,AI对话能力不仅能显著提升用户体验,还能有效降低运营成本。 但对于大多数开发团队而言…

DFS 序 O(1) 求 LCA

抛弃欧拉序,拥抱DFN。更新日志 2025/11/03:开工。思路 查询 \((dfn_u,dfn_v]\) 最小深度的节点的父亲即可。读者自证不难。

@pytest.fixture和setup/teardown

一.定义fixture 定义:@pytest.fixture是 Pytest 中用于定义​​测试夹具(Fixture)​​的核心装饰器,其核心作用是​​为测试用例提供可复用的资源初始化、数据准备和清理逻辑​​ 二:代替setup 和 teardownsetup =…

矿山通信如何实现全域一体化?迈威为煤矿装上了“智慧神经网络”

矿山通信如何实现全域一体化?迈威为煤矿装上了“智慧神经网络”在刚刚落幕的第二十一届中国国际煤炭采矿技术展上,全球矿业的目光再次聚焦于一个核心议题:数字化转型如何真正在矿山落地。作为这一进程的“神经中枢”…

Java异常处理实战精要:构建稳定应用的基石

在Java项目的开发历程中,异常处理是衡量代码健壮性与开发者专业度的重要标尺。一个处理得当的异常体系,能像程序的免疫系统一样,有效抵御外部的意外干扰,维持内部逻辑的稳定运行。本文将系统性地梳理Java项目中异常…

€$P2025

省流:唇膜玩 由于特殊原因把前面部分跳过 刚进考场被监考员扔到没人坐的一组的第一排,然后我的右边的右边的后面是小玉米 14:27 准时看 T1,这个 \(\frac{n}{2}\) 的限制导致最多只有一个部门会因为限制导致结果变小…

CSP2025 补题

游记没什么好搬的,链接。 T1 发现只会有一个超限,贪心换一下就行了。 T2 首先暴力枚举 \(k\) 拿边跑 MST 的复杂度是 \(O(2^knk + 2^kn\log nk)\) 的,考虑将 Kruskal 的 sort 换成 std::merge 即可通过,复杂度 \(O…

哈希学习总结

基本思想 哈希的核心思想在于,将输入映射到一个值域较小、可以方便比较的范围。 性质两个相同的元素哈希值相同。 两个不同的元素哈希值不同(若相同则称为冲突)。一维哈希(字符串哈希) 对于字符串 \(s\),其哈希值…

142.环形链表 II

var detectCycle = function(head) {if(!head)return null;let pre=head,cur=head;while(cur&&cur.next){pre=pre.next;cur=cur.next.next;if(pre===cur){let temp=head;while(temp!==pre){pre=pre.next;temp=…

2025 年 11 月制冷设备厂家推荐排行榜,小型制冷设备,空调制冷设备,工业制冷设备,商用制冷设备,大型制冷设备,制冷设备安装与维修服务公司推荐

2025 年 11 月制冷设备厂家推荐排行榜:小型、空调、工业、商用及大型制冷设备综合指南 行业背景与发展趋势 制冷设备行业作为现代工业和商业基础设施的重要组成部分,正经历着技术革新和市场需求的深刻变革。随着节能…

从创作到分析全搞定!2025公众号效率工具深度测评,这波升级95%的人还不知道

从创作到分析全搞定!2025公众号效率工具深度测评,这波升级95%的人还不知道作为新媒体运营者,你是否常陷入这些困境:熬夜赶稿后排版格式反复出错,跨平台分发时样式全乱;拟标题时反复修改仍无爆款潜力,配图怕侵权…

20232304 2025-2026-1 《网络与系统攻防技术》实验四实验报告

20232304 2025-2026-1 《网络与系统攻防技术》实验四实验报告 1.实验内容 一、恶意代码文件类型标识、脱壳与字符串提取 对提供的rada恶意代码样本,进行文件类型识别,脱壳与字符串提取,以获得rada恶意代码的编写作者…

k8s-java应用部署(4)

我们一个常见的应用,一般都会涉及到访问数据库,配置等外部资源。同时也将自己的能力提供给外部访问,前面已经介绍pod的内外网络通信 k8s-Pod中的网络通信(3) 这次直接做一个java应用, 开发——打镜像——配置并部署…

指数函数和对数函数

一、指数函数的定义与特点:指数函数的一般形式为f(x) = a^x,其中a为常数,x为自变量。它以自然常数e为底,也可以选择其他正实数作为底。指数函数的特点如下:1. 当a>1时,随着x的增大,指数函数迅速增大,呈现指…

单目三角化原理 - MKT

单目三角化原理 为什么V T 的最后一列就是解?第一和第二帧产生地图点后,如果进来第三帧,如何定位第三帧,并将其建立的新地图点和现有地图点合并一个坐标系。考虑到第三帧和第二帧的相对位姿尺度未必与第一帧…

[CEOI 2017] Sure Bet

神秘题目,本人用的贪心做的,发现一个二分写法,故记录一下。 题意 有 \(2N\) 个灯泡,分为 \(A\) 组和 \(B\) 组各 \(N\) 个。 你可以从中选取任意个灯泡,每选取一个灯泡需要花费 1 的代价。 在你选取完之后,系统会…

Java数组——三种初始化及内存分析,数组的基本特点,下标越界与小结

Java数组——三种初始化及内存分析,数组的基本特点,下标越界与小结内存分析 堆:存放new的对象与数组; 可以被所有线程共享,不会存放别的对象引用。栈:存放基本变量类型(会包含这个基本类型的具体数值) 引用对象…

LeRobot v0.4.0 正式发布:全面提升开源机器人的学习能力

我们非常高兴地宣布,LeRobot 迎来一系列重大升级,让开源的机器人学习比以往更强大、更可扩展、也更易用!从重构的数据集到灵活的编辑工具、新的仿真环境,以及面向硬件的全新插件系统,LeRobot 正在持续演进,以满足…

QPS、TPS、PV、UV、并发量

QPS:每秒查询数(Queries Per Second)核心定义:每秒向系统发起的「查询请求次数」(比如接口调用、数据库查询)。 关键特点:只统计 “请求发起”,不要求请求必须完整完成(比如只读数据的请求,只要发出去就算一…