C语言-什么是尾递归

尾递归,C语言实现 普通递归在执行时需要在保存当前数据,并在内存中重新开辟栈。所以普通递归的空间复杂度比较高,效率比较低。所以,我们一般倾向于把递归方式转化成迭代的方式进行计算。但是如果递归语句是整个函数的最后一个语句,则无需保存当前数据,并重新开辟栈。可以直接在当前的栈中继续读取/写入数据。这种递归方式的效率就比较高。递归语句是整个函数的最后一个语句,这种方式称之为尾递归。我们还是用计算sum = 1 + 2 + 3 + … + n举例: int add1(int n) //普通递归 { if (n == 0) // 阅读详情

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的阶乘的值)也就是 1 1 1 ,于是在使用函数的时候,第二个参数传递一个 1 1 1 。

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 参考

  1. What is tail recursion - StackOverflow?
  2. 数据结构与算法分析:C语言描述(原书第2版) - Mark Allen Weiss
算法精解-C语言描述 递归和尾递归 (图解+实例) 递归是一种强大的方法,它允许一个对象以其自身更小的形式来定义自己。 让我们来观察一下自然界中出现的递归现象:蕨类植物的叶子,每片叶子叶脉中的小分支都是整片叶子的较小缩影;又或者两个反光的物体,相互映射对方渐远的影像。这样的例子使我们明白,尽管大自然的力量是强大的,在许多方面它那种出乎意料的简洁更让我们觉得优美。同样的道理也可以用在递归算法上,从很多方面来说递归算法都是简洁而优美的,而且非常强大。 阅读详情

相关推荐

PyMOL下载安装教程(附安装包)2025最新版(PyMOL 3.0.3)

本文详细介绍了Windows系统下PyMOL分子建模软件的安装教程。主要内容包括:提供安装包下载链接,强调需关闭杀毒软件;分步骤讲解管理员身份运行安装程序、接受协议、选择安装路径等安装流程;重点说明如何配置隐藏文件夹ProgramData中的许可证文件;最后提供基础使用技巧和常见问题解决方案。该教程适用于需要制作高质量分子结构图的科研人员和新手用户,帮助完成软件部署并顺利开展工作。

progalchemist的博客 3798

C语言 尾递归

引用文章:尾递归 文章目录定义原理实例1、首先展示我们正常计算阶乘的代码2、尾递归 定义 如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。 原理 当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为

Dontla的博客 1429

Canoe CAPL TCP通信避坑指南:从OnTcpConnect回调不触发到Socket句柄管理

本文详细解析了Canoe CAPL中TCP通信的常见问题与解决方案,包括OnTcpConnect回调不触发的三大原因、Socket句柄管理的内存泄漏风险、数据发送截断规则等。通过实战案例和代码示例,帮助开发者规避TCP通信中的典型陷阱,提升车载网络测试的稳定性和可靠性。

weixin_26976635的博客 358

【C语言】——尾递归

递归效率太低?那上尾递归!

yusjushd的博客 3215

⚡【C++要笑着学】(13) STL介绍:什么是STL标准模板库 | STL的版本 | 浅谈STL的缺陷

再讲解函数模板之前先对STL做一个小铺垫,本篇将对STL做一个简单的介绍。

有道无术, 术尚可求。有术无道, 止于术。 3034

全志 camera: 4路AHD摄像头转MIPI

TP2815 驱动模块主要实现将 4 路的 AHD/TVI/CVBS Camera 的数据转换为 MIPI 数据,从而实现在 T527 端来对数据进行处理和送显。

Linux驱动开发工程师,熟悉Linux/Android底层驱动、TP、显示、IIC、网络等通用外设调试适配,Linux上层应用开发。常用平台瑞芯微、全志、恩智浦等。 9094

C语言尾递归知识及代码示例

本文将介绍C语言中尾递归的概念、特点以及如何使用尾递归解决实际问题。同时,给出一个尾递归的代码示例。

2301_80220607的博客 948

笔试题-2023-乐鑫-数字IC设计【纯净题目版】

笔试题-2023-乐鑫-数字IC设计【纯净题目版】

w0shishabi的博客 1777

【C语言】尾递归

讲了尾递归特点及其使用思路,欢迎大家观看

2501_91427718的博客 976

【异常】Claude Code CLI工具 模型加载异常 /init报错排查与完整解决方案 There‘s an issue with the selected model (glm-5). It

此类跨厂商模型配置报错,核心诱因是API生态的兼容性不匹配。优先使用工具原生支持的模型体系,可快速解决90%以上的同类报错;若需跨厂商使用目标模型,需完成API端点、鉴权密钥、模型ID的全链路适配,确保全流程配置合规,即可完成异常修复。

奶爸的编程之路,也就一周冷个三天 3703

使用机器学习算法预测黄金价格

在本文中,我们将使用线性回归、XGBoost和LSTM这三种不同的机器学习算法来预测黄金价格。接下来,我们使用线性回归、XGBoost和LSTM分别建立了模型,并进行了训练和预测。这些机器学习算法可以帮助我们对黄金价格进行预测,但请注意,市场价格受多种因素影响,因此预测结果可能并不完全准确。接下来,我们需要对数据进行一些预处理,以便用于机器学习模型的训练和测试。我们将使用80%的数据作为训练集,20%的数据作为测试集。最后,我们将绘制每个模型的预测结果,并将其与实际价格进行比较,以评估模型的性能。

QromMatlab的博客 1316

c语言的尾递归,递归与尾递归(C语言)

原文:递归与尾递归(C语言)[转] 作者:archimedes 出处:http://www.cnblogs.com/archimedes/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 在计算机科学领域中,递归式通过递归函数来实现的.程序调用自身的编程技巧称为递归( recursion). 一个过程或函数在其定...

weixin_36008052的博客 621

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【C语言入门】尾递归优化

递归是计算机科学中的一种经典算法思想,通过函数调用自身实现。尽管递归在算法设计中广泛应用,但其栈空间复杂度为O(n),容易导致栈溢出问题。尾递归是递归的一种特殊形式,其递归调用是函数的最后一个操作,通过引入累积器参数,将中间结果通过参数传递,避免依赖上一层栈帧的状态。尾递归优化通过栈帧复用,将递归的栈空间复杂度从O(n)降为O(1),解决了大规模递归的栈溢出问题。编译器通过识别尾调用条件,将递归调用转换为跳转指令,实现栈帧复用。

Mr_-G,一名勤奋底层软件开发工程师的博客 1166

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

c语言尾递归,C语言——递归与尾递归

在计算机科学领域中,递归式通过递归函数来实现的。程序调用自身的编程技巧称为递归( recursion)。一个过程或者函数在其定义或者说明中有直接或者间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题类似的规模较小的问题来求解,递归策略只要一些的程序即可形容出解题过程所需要的屡次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需...

weixin_28689969的博客 482

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

C语言之尾递归

昨天被问到了尾递归及编译器对它的处理相关,一直对它没有研究过,解释得很含糊。 回来查了下,记录如下: 递归有线性递归(普通的递归)和尾递归。 由于尾递归的特殊性,一般的编译器会做些特殊处理。因此,在效率和开销上,比普通递归好。 举个例子,计算n! 1)线性递归: type recurve(long n) {       return  (n == 1) ? 1 : n * re

Kayven@数据 1万+

诺禾-C语言实现尾递归求7的阶乘

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。 当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活跃记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也

yyone123的博客 1724

C语言的递归与尾递归

递归的优点为某些编程问题提供了很简单的解决方案,缺点是一些递归算法会快速消耗计算机的内存资源。有时递归算法不是很好阅读和维护。尾递归是最简单的递归形式,它的特点就是将递归调用放在函数的末尾,即正好在return语句之前。C语言允许函数调用自己,这种调用过程就叫递归。

weixin_40763897的博客 371

c语言尾递归,漫谈递归:从汇编看尾递归的优化

对于尾递归,很多人的理解仅局限于它是递归和尾调用的一个合体,比普通递归效率高。至于效率为什么高,高在哪,可能没有深究过。尾调用要说尾递归,得先说尾调用。我理解的尾调用大概是这么一种情况:函数A里面调用了函数B。函数B执行后,函数A马上返回。也就是说调用函数B(并返回执行结果)是函数A所做的最后一件事。相当于执行完函数B后,函数A也就执行完。因此在执行函数B时,函数A的栈帧其实是已经大部分没用了,可...

weixin_39588445的博客 330

c语言尾递归,C ++中的尾递归

尾递归是尾调用的一种特殊情况。尾部调用是编译器可以看到的,从被调用函数返回时无需执行任何操作-本质上将被调用函数的返回变为自己的。编译器通常可以执行一些堆栈修复操作,然后跳转(而不是调用)到被调用函数的第一条指令的地址。除了消除一些回叫之外,与此有关的一大优点是,您还可以减少堆栈使用量。在某些平台上或在OS代码中,堆栈可能会非常有限,而在台式机中x86 CPU这样的高级计算机上,减少堆栈使用量会提...

weixin_42393272的博客 361

斐波那契数列c语言链表,尾递归 - 以斐波那契数列为例说明(示例代码)

前言:今天上网看帖子的时候,看到关于尾递归的应用(http://bbs.youkuaiyun.com/topics/390215312),大脑中感觉这个词好像在哪里见过,但是又想不起来具体是怎么回事。如是乎,在网上搜了一下,顿时豁然开朗,知道尾递归是怎么回事了。下面就递归与尾递归进行总结,以方便日后在工作中使用。1、递归关于递归的概念,我们都不陌生。简单的来说递归就是一个函数直接或间接地调用自身,是为直接或间...

weixin_36286774的博客 429

快速排序尾递归版C语言

  看>,一道题.尾递归,循环换递归.思想很好,用处很大.  没有华丽的性能提高,只是展现尾递归的思想.确实,快速排序我还并未搞懂.先继续进行吧.  贴~ /* 2011-04-20-21.32.c -- 快速排序尾递归版 */ #include #include #define SIZE (22) int main (void) ; void printArray (const int * const array, const int size) ; void swap (int

让阳光照进心里 2086

c语言用尾递归实现单向链表的逆向排列

c++的stl很好用,c语言的简单链表最近几年几乎不用了。最近一次笔试,遇到了这题,就十分捉急。当时想到似乎得用递归。现在算是完成了。 以下以简单的单向链表(链表头结点跟普通结点一致)为例。 #include typedef struct Node { int i; struct Node *pNext; } Node; Node *createList(const int *dat

u010607621的博客 852

C 语言尾递归求阶乘,9.1 函数:尾递归计算阶乘(factorial)

最简单的递归形式就是把递归调用置于函数的末尾,即正好在return语句之前,这种形式的递归被称为尾递归 (tail recursion),其形式相当于循环。/* 递归计算阶乘 */#includelong fact(int n);long rfact(int n);int main(){int num;printf("this program calculates factorials.\n");...

weixin_39614675的博客 1253

C语言以尾递归的形式计算阶乘的一个函数实现的代码

如下资料是关于C语言以尾递归的形式计算阶乘的一个函数实现的代码。 #include “facttail.h” int facttail(int n, int a) { if (n < 0) return 0; else if (n == 0) return 1; else if (n == 1) return a; else } ...

weixin_44006606的博客 546

c语言 尾递归,尾递归的笔记

尾递归,在工作中从来没有用到,仅仅是听过。早晨看文章在Java中谈尾递归–尾递归和垃圾回收的比较的时候,觉得蛮有意思的,尾递归居然可以和JVM的GC放在一起比较,所以搜索了一些文章,作为收藏。如下截取自在Java中谈尾递归–尾递归和垃圾回收的比较,部分文字做了删减,但不影响理解。尾递归优化解决的是内存溢出的问题,而垃圾回收解决的是内存泄露的问题内存泄露:指程序中动态分配内存给一些临时对象,但是对象...

weixin_39877050的博客 183
上一篇: C语言-二维数组做函数的参数
下一篇: C语言-链表的创建头插法和尾插法(有无头节点)
syzdev
博客等级 码龄9年 852粉丝 149原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值