C语言-什么是尾递归
文章目录
- 1 尾递归简介
- 2 总结
- 3 参考
1 尾递归简介
想必大家都知道递归是什么,第一次接触尾递归,首先要从它的定义说起:
尾递归:当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作。
举一个简单的例子,用递归算阶乘:
int factorial(int n)
{ if(n == 0 || n == 1) {return 1;} else {return n * factorial(n-1); }
}
现在我们模拟一下程序的过程,例如当 n = 5
的时候:
factorial(5)
5 * factorial(4)
5 * (4 * factorial(3))
5 * (4 * (3 * factorial(2)))
5 * (4 * (3 * (2 * factorial(1))))
5 * (4 * (3 * (2 * 1)))
5 * (4 * (3 * 2))
5 * (4 * 6)
5 * 24
120
上述的递归过程中,“最长”的部分,即代表着此刻占用的栈内存最大,所以在一般的递归中,它的内存占用,先是增大,后来到达一个峰值,然后缩小。
下面给出尾递归的实现方法,看到这段代码后,第一印象会发现函数参数多了一个 tmp
,事实上 tmp
代表着 1!(1的阶乘的值)也就是 111 ,于是在使用函数的时候,第二个参数传递一个 111 。
int factorial(int n, int tmp)
{if(n == 0) {return 1;} else if (n == 1) {return tmp;} else {return factorial(n - 1, n * tmp);}
}
我们同样模拟一下程序的过程,例如当 n = 5
的时候:
factorial(5, 1)
factorial(4, 5)
factorial(3, 20)
factorial(2, 60)
factorial(1, 120)
120
通过这个过程,我们可以发现,它完全对应着尾递归的定义,即当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作。
尾递归比线性递归(第一个例子称为线性递归)多一个参数,这个参数是上一次调用函数得到的结果,并且每次递归的时候都会保留着上次递归的结果,它不属于表达式的一部分,而且它需要回归的数据,本身已经通过参数携带,所以回归过程中不用做任何操作,所以,相比较线性递归,尾递归占用的栈内存是恒定的。
2 总结
既然尾递归相比线性递归解决了线性递归致命的缺点(stack overflow风险),是不是更提倡使用尾递归呢?
答案不是的,参考数据结构与算法分析-C语言描述中的一个例子,打印一个链表:
/* Bad use of recursion : Printing a linked list */
/* No header */void PrintList(List L)
{if (L != NULL) {PrintElement(L->Element);PrintList(L->Next);}
}
在这里使用尾递归就是一个不好的例子,因为没有什么需要存储,在递归结束调用的时候,实际上并没有需要存储的值,因此,我们就可以带着在第一次递归调用中已经用过的那些值, goto
到函数的顶部,下面是改进后的程序(记住,应该更加自然的使用 while
循环结果去去除尾递归,这里使用 goto
只是为了说明编译器是如何自动地去除尾递归)。
/* Printing a linked list non-recursively */
/* Uses a mechanical translation */
/* No header */void PrintList(list L)
{top:if (L != NULL) {PrintElement(L->Element);L = L->Next;goto top;}
}
不用递归去打印一个链表,事实上尾递归的去除是如此的简单,以至于某些编译器可以自动完成,所以这些工作不用程序员去完成,但是即使如此,最好还是在编写程序时避免出现尾递归。
3 参考
- What is tail recursion - StackOverflow?
- 数据结构与算法分析:C语言描述(原书第2版) - Mark Allen Weiss
C语言-什么是尾递归相关推荐
- c语言尾递归,C语言——递归与尾递归
在计算机科学领域中,递归式通过递归函数来实现的.程序调用自身的编程技巧称为递归( recursion). 一个过程或者函数在其定义或者说明中有直接或者间接调用自身的一种方法,它通常把一个大型复杂的问题 ...
- 什么是尾递归,尾递归的优势以及语言支持情况说明
今天在进行数据排序时候用到递归,但是耗费内存太大,于是想找一找有没有既提升效率又节省内存的算法,然后发现尾递归确实不错,只可惜php并没有对此作优化支持. 虽然如此,但还是学习了,下面总结一下: 尾递 ...
- php 尾递归,关于尾递归的使用详解
这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归. 尾递归的概念 尾递归(Tail Recursion)的概念是递归概念的一个子集.对于普通的递归,由于必须要记住递归 ...
- python递归和循环的区别_递归与伪递归区别,Python 实现递归与尾递归
递归函数在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函 数.(1) 递归就是在过程或函数里调用自身.(2) 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出 ...
- 什么是尾递归?(知乎转载)
什么是尾递归,排名第一的赞的回答是: function story() { 从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story() // 尾递归,进入下一个函数不再需要上一个 ...
- 哥特巴赫猜想 尾递归 湘潭孕妇之后的自我检讨
毫不相干的三个关键词,没人能想到下面要写些什么. 本文是关于学习方法和思考方式的自我检讨. 1. 哥特巴赫猜想 从小学开始老师告诉我们有个伟大的猜想叫做哥特巴赫猜想,中国有位伟大的数学家陈景润 ...
- php 尾递归,又见尾递归
这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归. 尾递归的概念 尾递归(Tail Recursion)的概念是递归概念的一个子集.对于普通的递归,由于必须要记住递归 ...
- 《javascript语言精粹》学习笔记 - 递归函数
递归函数就是会直接或者间接地调用自身的一种函数.递归是一种强大的编程技术,它把一问题分解为一组相似的子问题,每一个都用一个寻常解去解决.一般来说,一个递归函数调用自身去解决它的子问题. 书上的一个小例 ...
- 《JavaScript语言精粹》学习笔记(函数(2))
<JavaScript语言精粹>学习笔记(函数(2)) 函数(Functions) 参数(Arguments) 当参数被调用时,会得到一个"免费"的参数数组argume ...
最新文章
- Visual Studio 2017软件安装教程
- SpringBootSpring --- Redis 集成 Error creating bean with name 'enableRedisKeyspaceNotificationsIniti
- 网络编程释疑之:TCP连接拔掉网线后会发生什么
- 转载:中年程序猿的迷茫,你还在深究技术吗?
- c++中的函数适配器
- 知乎招聘搜索算法实习生!邀你共建知乎搜索引擎!
- linux fish颜色配置,如何在 Linux 中安装、配置和使用 Fish Shell?
- 专门为某种用途而设计的计算机 称为,专门为某种用途而设计的计算机,称为计算机...
- 服务器显示器超频,电脑显示器超频怎么恢复正常 电脑显示器超频是什么原因...
- SPSSAU入门---浅谈问卷设计到数据分析之间的联系
- python关系图谱_利用Python+Gephi构建LOL全英雄间的关联图谱
- 搜狗高级测试经理诸葛东明谈基于AI图像识别的输入法性能测试实践
- 周问题回复-滤波器-锁相环BL参数及环路滤波器参数问题
- 版权微talk | 两部门发文,拟出台相关方案,全面加强知识产权保护
- python 二值化细化_Python - 图像的细化(骨架抽取)
- 动态生成表格案例(HTML+CSS+JS)
- 推荐系统实践(五)----基于图的推荐算法
- Linux下干净卸载mysql详解
- Android tcp与网络调试助手初入了解
- 《hanhan的创作纪念日:From 2020 To 2023》
热门文章
- 空腹吃香蕉对身体好吗?哪些水果不宜空腹吃
- Buffer Cache Hit Ratio
- Windows 8实用窍门系列:10.Windows 8的基本变换和矩阵变换以及AppBar应用程序栏
- Oracle 10g OCP 042 题库 1-30 题 共168题
- 如何正确的阅读Datasheet?
- Lintcode 167. 链表求和 221. 链表求和 II 题解
- 微信 WEUI 的 switch button 精简提取
- nyist 541最强DE 战斗力
- 怎样把SharePoint中文备份恢复到英文版,修改sharepoint站点语言
- HDU 1236 ( 排名 )