当前位置: 首页 > article >正文

C++杂记——尾递归

文章目录

      • 递归
      • 尾递归
      • 递归的消除

递归

传统地递归过程就是函数调用,涉及返回地址、函数参数、寄存器值等压栈(在x86-64上通常用寄存器保存函数参数),这样做的缺点有二:

  • 效率低,占内存
  • 如果递归链过长,可能会栈溢出

尾递归

尾递归是递归的一种特殊情形,是一种特殊的尾调用,即在尾部直接调用自身的递归函数。核心思想是边调用便产生结果。尾递归涉及在最后一行的递归调用,尾递归可以通过将递归调用变成goto语句并在其前面加上对函数每个参数的赋值语句而手工消除。它模拟了递归调用,因为没有什么需要存储;在递归调用结束后,实际上没有必要知道存储的值。因此,我们就可以带着在一次递归调用中已经用过的那些值goto到函数顶部。

这是尾递归:

int function f(x) {
   if (x === 1) return 1;
   return f(x-1);
}
int function f(x) {
   if (x === 1) return 1;
   return 1 + f(x-1);
}

后者不是尾递归,是因为该函数的最后一步操作是用1加上f(x-1)的返回结果,因此,最后一步操作不是调用自身。注意,后面这段代码的递归也发生在函数末尾,但它不是尾递归。**尾递归的判断标准是函数运行最后一步是否调用自身,而不是是否在函数的最后一行调用自身。**因此阶乘n!的递归求法,尽管看起来递归发生在函数末尾,其实也不是尾递归:

int function factorial(n) {
   if (n === 1) return 1;
   return n * factorial(n-1); // 最后一步不是调用自身,因此不是尾递归
}

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

特点:在尾部调用的是函数自身,可通过优化使得计算仅占用常量栈空间

递归的消除

使用尾递归可以带来一个好处:因为进入最后一步后不再需要参考外层函数(caller)的信息,因此没必要保存外层函数的stack,递归需要用的stack只有目前这层函数的,因此避免了栈溢出风险。

一些语言提供了尾递归优化,当识别出使用了尾递归时,会相应地只保留当前层函数的stack,节省内存。

所以,在没有尾递归优化的语言,如java, python中,鼓励用迭代iteration来改写尾递归;在有尾递归优化的语言如Erlang中,鼓励用尾递归来改写其他形式的递归。


http://www.kler.cn/a/567035.html

相关文章:

  • ‌Tomcat 8.0.12安装流程
  • 【Android】Android Studio 中文乱码问题解决方案
  • 架构案例:从初创互联网公司到分布式存储与反应式编程框架的架构设计
  • 6.指针学习
  • Linux操作系统:基于 Linux 的智能家居系统开发与实现 —— 以 FS - MP1A 嵌入式开发板为例
  • Java多线程与高并发专题——从AQS到ReentrantLock
  • 2025 蓝桥杯 Python 组部分题目解析
  • ffmpeg常用方法(一)
  • 【后端】Docker一本通
  • AI 驱动的智慧大脑:打造企业动态知识库,开启高效管理新时代
  • Vue核心知识:动态路由实现完整方案
  • 单细胞分析(19)—— 单细胞转录组基因集评分方法
  • 代码随想录算法训练营day49(0217)
  • MathJax v2版本中网络慢导致出现 Math Processing Error 问题处理
  • 哔哩哔哩IT私塾python爬虫视频教程中的项目文件
  • 【Maui】系统找不到指定的文件Xamarin.Android.Aapt2.targets
  • Python的那些事第三十六篇:基于 Vega 和 Vega-Lite 的数据可视化解决方案,Altair 声明式可视化库
  • 全国普通高等学校名单
  • Linux与UDP应用1:翻译软件
  • Spring Boot 3.x 基于 Redis 实现邮箱验证码认证