解决递归栈溢出:尾递归

原文:尾递归_百度百科

尾递归

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。

目录

  1. 1 原理
  2. 2 实例

原理

当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。

实例

为了理解尾递归是如何工作的,让我们再次以递归的形式计算阶乘。首先,这可以很容易让我们理解为什么之前所定义的递归不是尾递归。回忆之前对计算n!的定义:在每个活跃期计算n倍的(n-1)!的值,让n=n-1并持续这个过程直到n=1为止。这种定义不是尾递归的,因为每个活跃期的返回值都依赖于用n乘以下一个活跃期的返回值,因此每次调用产生的栈帧将不得不保存在栈上直到下一个子调用的返回值确定。现在让我们考虑以尾递归的形式来定义计算n!的过 [1]  程。

这种定义还需要接受第二个参数a,除此之外并没有太大区别。a(初始化为1)维护递归层次的深度。这就让我们避免了每次还需要将返回值再乘以n。然而,在每次递归调用中,令a=na并且n=n-1。继续递归调用,直到n=1,这满足结束条件,此时直接返回a即可。

代码实例3-2给出了一个C函数facttail,它接受一个整数n并以尾递归的形式计算n的阶乘。这个函数还接受一个参数a,a的初始值为1。facttail使用a来维护递归层次的深度,除此之外它和fact很相似。读者可以注意一下函数的具体实现和尾递归定义的相似之处。

示例3-2:以尾递归的形式计算阶乘的一个函数实现 [1] 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

/*facttail.c*/

#include"facttail.h"

/*facttail*/

int facttail(int n, int a)

{

    /*Compute a factorialina tail - recursive manner.*/

     

    if (n < 0)

        return 0;    

    else if (n == 0)

        return 1;    

    else if (n == 1)

        return a;

    else

        return facttail(n - 1, n * a);

}

示例3-2中的函数是尾递归的,因为对facttail的单次递归调用是函数返回前最后执行的一条语句。在facttail中碰巧最后一条语句也是对facttail的调用,但这并不是必需的。换句话说,在递归调用之后还可以有其他的语句执行,只是它们只能在递归调用没有执行时才可以执行 [1]  。

尾递归是极其重要的,不用尾递归,函数的堆栈耗用难以估量,需要保存很多中间函数的堆栈。比如f(n, sum) = f(n-1) + value(n) + sum; 会保存n个函数调用堆栈,而使用尾递归f(n, sum) = f(n-1, sum+value(n)); 这样则只保留后一个函数堆栈即可,之前的可优化删去。

也许在C语言中有很多的特例,但编程语言不只有C语言,在函数式语言Erlang中(亦是栈语言),如果想要保持语言的高并发特性,就必须用尾递归来替代传统的递归。

原文的说法是错误的:原文如下:

一种算法, 用于计算机编程技术.

尾递归是针对传统的递归算法而言的, 传统的递归算法在很多时候被视为洪水猛兽. 它的名声狼籍, 好像永远和低效联系在一起.

尾递归就是从最后开始计算, 每递归一次就算出相应的结果, 也就是说, 函数调用出现在调用者函数的尾部, 因为是尾部, 所以根本没有必要去保存任何局部变量. 直接让被调用的函数返回时越过调用者, 返回到调用者的调用者去.

以下是具体实例:

线性递归:

1

2

3

4

5

long Rescuvie(long n) {

    return (n == 1) ? 1 : n * Rescuvie(n - 1);

}

尾递归:

1

2

3

4

5

6

7

8

9

10

11

12

long TailRescuvie(long n, long a) {

    return (n == 1) ? a : TailRescuvie(n - 1, a * n);

}

long TailRescuvie(long n) {//封装用的

     

    return (n == 0) ? 1 : TailRescuvie(n, 1);

}

当n = 5时

对于线性递归, 他的递归过程如下:

Rescuvie(5)

{5 * Rescuvie(4)}

{5 * {4 * Rescuvie(3)}}

{5 * {4 * {3 * Rescuvie(2)}}}

{5 * {4 * {3 * {2 * Rescuvie(1)}}}}

{5 * {4 * {3 * {2 * 1}}}}

{5 * {4 * {3 * 2}}}

{5 * {4 * 6}}

{5 * 24}

120

对于尾递归, 他的递归过程如下:

TailRescuvie(5)

TailRescuvie(5, 1)

TailRescuvie(4, 5)

TailRescuvie(3, 20)

TailRescuvie(2, 60)

TailRescuvie(1, 120)

120

很容易看出, 普通的线性递归比尾递归更加消耗资源, 在实现上说, 每次重复的过程

调用都使得调用链条不断加长. 系统不得不使用栈进行数据保存和恢复.而尾递归就

不存在这样的问题, 因为他的状态完全由n和a保存.

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

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

相关文章

mysql blob hex_数据库的完整备份与恢复 quot;--hex-blobquot; - - ITeye博客

闲言少絮&#xff0c;这个程序利用MySql数据库自带小程序进行数据库的备份和还原。这两个程序分别是&#xff1a;mysql.exe和mysqldump.exe。这两个程序在您安装Mysql数据库的时候会自动安装到数据库的bin目录。这两个程序存在的目录为&#xff1a;C:\Program File\MySQL\MySQL…

python实现多表格合并_用python实现多个表格合并按字段去重

需求 在xx银行项目中遇到的一个需求&#xff0c;是将系统中的8张余额表下载到指定的文件夹中&#xff0c;然后从文件夹中读取这8张余额表&#xff0c;将其合并为一张余额汇总表&#xff0c;在合并的时候要将组合名称重复的多行数据去重&#xff0c;并将其所对应的余额相加 实现…

C# Lambda 和 匿名函数的GC总结

关于Lambda和 匿名函数&#xff0c;闭包的GC&#xff0c;其实可以总结为两条。 为了方便理解&#xff0c;以举例说明&#xff0c;首先我们定义变量&#xff0c;静态变量&#xff0c;以及函数如下&#xff1a; static int staticVariable 0;int variable 0;private void Func…

Unity使用sdkmanager命令行工具安装Android SDK

转自&#xff1a;使用sdkmanager命令行工具安装Android SDK Unity自带的SDKManager没有GUI工具&#xff0c;如果需要其他android SDK版本&#xff0c;可以用Unity自带的SDKManager使用命令行方式安装&#xff1a; 例如安装android27,将目录切到Unity的Android SDK安装目录后&…

mysql c api 封装_封装MySQL C API 基本操作

根据我的以前的文章 http://blog.csdn.net/skyhuangdan/article/details/21099929 链接数据库成功后进行封装。我封装类使用的是VS2005下的win32控制台应用程序编写&#xff0c;预编译头文件了的。所以要在 stdafx.h 里面加入 &#xff1a;#include "CMySQL.h"现在代…

android中怎么保存checkbox中的checked属性_Vue 精粹:v-model指令在组件中怎么玩

最近在写组件的时候&#xff0c;遇到了 v-model 的使用问题&#xff0c;在 Vue 官方文档中&#xff0c;有两小端内容是关于 v-model 指令在组件中的使用,查阅文档后&#xff0c;依然不得要领&#xff0c;最后几番折腾&#xff0c;理论结合实践&#xff0c;终于领悟其精髓&#…

xcode 自动化出包

iOS提高效率之Xcodebuild自动打包总结 - 简书 使用xcodebuild命令进行自动化打包 - 简书 Add a profile to the provisioningProfiles dictionary in your Export Options property list. | 亂馬客 - Re:從零開始的軟體開發生活

linux location root访问文件夹404_如何使网站支持https访问?nginx配置https证书

购买SSL证书要想使用https访问你的网址&#xff0c;首先得拥有颁发的SSL证书。我使用的是免费版&#xff0c;有效期为一年&#xff0c;过期后再重新申请。申请SSL证书购买后&#xff0c;可在搜索框输入证书关键字进入到控制台。点击证书申请&#xff0c;按照提示填写完相关信息…

c++头文件能否包含函数实现?

头文件即.h文件一般是用来写函数或者类的定义的&#xff0c;而函数的实现一般在cpp文件中完成&#xff0e;但是我也看到过一些文件就在头文件中同时定义函数和实现&#xff0e;打个比方我现在有个头文件名叫header.h,内容如下 #include <iostream>void printInt(int numb…

mysql rank函数_Sql 四大排名函数(ROW_NUMBER、RANK、DENSE_RANK、NTILE)简介

排名函数是Sql Server2005新增的功能&#xff0c;下面简单介绍一下他们各自的用法和区别。我们新建一张Order表并添加一些初始数据方便我们查看效果。表结构和初始数据Sql附上表结构和初始数据图&#xff1a;一、ROW_NUMBERrow_number的用途的非常广泛&#xff0c;排序最好用他…

git2.29.2.2怎么安装_MySQL5.5怎么安装

安装MySQL5.5的步骤&#xff1a;1、 官网下载mysql5.5下载地址&#xff1a;http://dev.mysql.com/downloads/mysql/5.5.html#downloads2、 安装mysql5.5注意&#xff0c;安装之前&#xff0c;请关闭杀毒软件。1)、 打开下载的mysql-5.5.53-winx64.msi2) 、点击下一步3)、 选中复…

h、cpp和hpp头文件之间使用区别

hpp&#xff0c;其实质就是将.cpp的实现代码混入.h头文件当中&#xff0c;定义与实现都包含在同一文件&#xff0c;则该类的调用者只需要include该hpp文件即可&#xff0c;无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中&#xff0c;不再生成单…

未声明spire。它可能因保护级别而不可访问_信息系统安全:访问控制技术概述...

1.访问控制基本概念身份认证技术解决了识别“用户是谁”的问题&#xff0c;那么认证通过的用户是不是可以无条件地使用所有资源呢&#xff1f;答案是否定的。访问控制(Access Control)技术就是用来管理用户对系统资源的访问。访问控制是国际标准ISO7498-2中的五项安全服务之一&…

c++反汇编与逆向分析技术揭秘_C++反汇编与逆向分析技术揭秘

一、单类继承在父类中声明为私有的成员&#xff0c;子类对象无法直接访问&#xff0c;但是在子类对象的内存结构中&#xff0c;父类私有的成员数据依然存在。C语法规定的访问限制仅限于编译层面&#xff0c;在编译过程中进行语法检查&#xff0c;因此访问控制不会影响对象的内存…

std::atomic原子操作

第十一节std::atomic原子操作_HITXJ的博客-CSDN博客_std::atomic用法

mysql 列级权限授予用户_mysql 用户及权限管理 小结

MySQL 默认有个root用户&#xff0c;但是这个用户权限太大&#xff0c;一般只在管理数据库时候才用。如果在项目中要连接 MySQL 数据库&#xff0c;则建议新建一个权限较小的用户来连接。在 MySQL 命令行模式下输入如下命令可以为 MySQL 创建一个新用户&#xff1a;新用户创建完…

python if name main 的作用_Python中if __name__ == __main__: 的作用

在很多python脚本中在最后的部分会执行一个判断语句if __name__ "__main__:"&#xff0c;之后还可能会有一些执行语句。那添加这个判断的目的何在&#xff1f; 在python编译器读取源文件的时候会执行它找到的所有代码&#xff0c;而在执行之前会根据当前运行的模块是…

php与mysql列表_PHP+Mysql+jQuery实现的查询和列表框选择

本篇文章主要介绍PHPMysqljQuery实现的查询和列表框选择&#xff0c;感兴趣的朋友参考下&#xff0c;希望对大家有所帮助。本文讲解如何通过ajax查询mysql数据&#xff0c;并将返回的数据显示在待选列表中&#xff0c;再通过选择最终将选项加入到已选区&#xff0c;可以用在许多…

range函数python2和3区别_range函数python2和3区别

range函数是一个用来创建算数级数序列的通用函数&#xff0c;返回一个[start, start step, start 2 * step, ...]结构的整数序列&#xff1b;py2中的range()函数用法&#xff1a;&#xff08;推荐学习&#xff1a;Python视频教程&#xff09; range()返回的是一个列表>>&…

Unity SRP自定义渲染管线 -- 2.Custom Shaders

本章将接着上一篇文章&#xff0c;在初步实现一个渲染管线后来创建自定义的shader。上一篇文章的链接 https://blog.csdn.net/yinfourever/article/details/90516602。在本章中&#xff0c;将完成以下内容&#xff1a; 写一个HLSL Shader定义constant buffer&#xff08;常量缓…