c语言 尾递归,尾递归的笔记
尾递归,在工作中从来没有用到,仅仅是听过。
早晨看文章在Java中谈尾递归–尾递归和垃圾回收的比较的时候,觉得蛮有意思的,尾递归居然可以和JVM的GC放在一起比较,所以搜索了一些文章,作为收藏。
如下截取自在Java中谈尾递归–尾递归和垃圾回收的比较,部分文字做了删减,但不影响理解。
尾递归优化解决的是内存溢出的问题,而垃圾回收解决的是内存泄露的问题
内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。
内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。
从定义上可以看出内存泄露是内存溢出的一种诱因,不是唯一因素。
自动垃圾回收机制的特点是:
解决了所有情况下的内存泄露的问题,但还可以由于其他原因内存溢出
针对内存中的堆空间
正在运行的方法中的堆中的对象是不会被管理的,因为还有引用(栈帧没有被清空)
与之相对,尾递归优化的特点是:
优化了递归调用时的内存溢出问题
针对内存中的堆空间和栈空间
只在递归调用的时候使用,而且只能对于写成尾递归形式的递归进行优化
正在运行的方法的堆和栈空间正是优化的目标
通过比较可以发现尾递归和GC是完全不一样的,JAVA不会是因为有GC所以不需要尾递归优化。
如下截取自Scala之尾递归,对递归和循环的区别有一个清晰的表述,蛮有意思的。
在把递归转换成尾递归中,有一个道理非常重要。递归是表达问题的一个方式,而循环是寻找解决问题的一个方式。尾递归的本质是循环而不是递归。因此在转换的过程中,不要把思路拘泥于递归上,而应该扩展到循环上。理论上来讲,任何递归都可以转换成循环,因为尾递归的本质是循环,因此任何递归也应该都可以转换为尾递归。
为如下这句话点赞。
递归是表达问题的一个方式,而循环是寻找解决问题的一个方式。
相比较而言,循环是比较反人类的,理解起来着实困难,比如树的遍历,
递归的解法为,先遍历左树,后遍历右树,再访问根节点,完了。
循环的解法就复杂了,除了循环,还需要用栈或者队列来做辅助,好难理解。
但如下这个推理过程,Jackie表示不能认同。
任何递归都可以转换成循环,因为尾递归的本质是循环,因此任何递归也应该都可以转换为尾递归。
关于Java中尾递归的优化,介绍了尾调用和尾调用的优化。
另外还有阿里的招聘广告,保存下来备用吧。
花名有孚,支付宝工程师
有希望加入支付宝的同学,可以把简历发到我的个人邮箱spidercoco@gmail.com
如下截取自线性递归和尾递归,给出了线性递归和尾递归的定义。
线性递归:即一般型的递归,一个函数直接或间接地调用自身,是为直接或间接递归,在调用过程中,需要压栈,可能会导致程序崩溃。
尾递归:尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去.
如下截取自尾递归。
分析了一大堆,那么尾递归到底有什么好处呢?呵呵,可读性好,代码强壮,易维护。
另外,不是所有的递归都可以变成尾递归的。
从老赵点滴 – 追求编程之美找到了如下文章,从样例代码看,使用的应当是C#,还好不影响理解。站点使用.net技术来构建,比较有特点。
奇怪的是没有找到《尾递归对时间与空间复杂度的影响(下)》的链接。
一般来说,递归需要有:边界条件、递归前进段和递归返回段。
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
注意:
递归就是在过程或函数里调用自身;
在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口。
递归的定义
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
递归的原理
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。虽然编译器能够优化尾递归造成的栈溢出问题,但是在编程中,我们还是应该尽量避免尾递归的出现,因为所有的尾递归都是可以用简单的goto循环替代的。
若非注明,均为原创,欢迎转载,转载请注明来源:尾递归的笔记
c语言 尾递归,尾递归的笔记相关推荐
- 诺禾-C语言实现尾递归求7的阶乘
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的.当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归.尾递归函数的特点是在 ...
- 《JavaScript语言精粹》学习笔记(函数(2))
<JavaScript语言精粹>学习笔记(函数(2)) 函数(Functions) 参数(Arguments) 当参数被调用时,会得到一个"免费"的参数数组argume ...
- 语言 提取列名_学习健明老师发布的R语言练习题的学习笔记(二)
学习者:骆栢维 题目来源:生信基石之R语言 中级10 个题目:http://www.bio-info-trainee.com/3750.html 备注:本文为笔者学习健明老师GitHub答案代码的学习 ...
- 数据结构(c语言版)笔记6,2020考研计算机《数据结构(C语言版)》复习笔记(6)
2020年计算机考研复习已经开始,新东方在线在此整理了2020考研计算机<数据结构(C语言版)>复习笔记(6),希望能帮助大家! 第六章 树知识点整理 树是n个结点的有限集合,非空时必须满 ...
- 独立式环境与宿主式环境————《标准C语言指南》读书笔记01
独立式环境与宿主式环境----<标准C语言指南>读书笔记01 在编写和转换一个C程序之前,需要考虑它的执行环境,因为这关系到源文件的内容(程序应当如何编写),也关系到转换后的程序能否正常执 ...
- 《Go语言圣经》学习笔记 第十一章 测试
<Go语言圣经>学习笔记 第十一章 测试 目录 go test 测试函数 测试覆盖率 基准测试 剖析 示例函数 注:学习<Go语言圣经>笔记,PDF点击下载,建议看书. Go语 ...
- 《Go语言圣经》学习笔记 第十章 包和工具
<Go语言圣经>学习笔记 第十章 包和工具 目录 包简介 导入路径 包声明 导入声明 包的匿名导入 包和命名 工具 注:学习<Go语言圣经>笔记,PDF点击下载,建议看书. G ...
- 《Go语言圣经》学习笔记 第九章 基于共享变量的并发
<Go语言圣经>学习笔记 第九章 基于共享变量的并发 目录 竞争条件 sync.Mutex互斥锁 syn.RWMutex读写锁 内存同步 syn.Once初始化 竞争条件检测 示例:并发的 ...
- 《Go语言圣经》学习笔记 第八章 Groroutines和Channels
<Go语言圣经>学习笔记 第八章 Groroutines和Channels 目录 Goroutines 实例:并发的Clock服务 实例:并发的Echo服务 Channels 并发的循环 ...
最新文章
- json取值_Mysql 中json数据存储读取
- 关于request取中文字符串变?的解决办法
- python3 字符串、十六进制字符串、数字、字节之间的转换
- Go进阶(8): map嵌套的两轮初始化
- C语言 | 基于MPU6050的卡尔曼滤波算法(代码类)
- html显示php值,HTML窗体加载显示通过PHP的十六进制值
- Composition-API
- Java 接口和类一些总结
- First Bad Version
- (自适应手机版)中英文双语响应式新材料类网站源码 HTML5新型环保材料网站织梦dedecms模板
- 2021年电赛仪器仪表类重难点
- 《TCP/IP详解卷一:协议》学习笔记
- python使用 Captcha 模块来生成验证码图片
- 如何用 Python 编写 Alfred Workflow
- 基于VUE的SVG动画处理(一)
- RDS-TMC(Traffic Message Channel)蕴藏的商机不可小视
- 中秋节快到了,一起用MATLAB绘制一款2.5D月饼叭
- 3D打印机T3升级corexy 制作过程(一)
- 猫眼电影某页面动态字体bypass
- 什么是 CDP 和 LLDP?