又见尾递归

简介:

这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归。

 

尾递归的概念

尾递归(Tail Recursion)的概念是递归概念的一个子集。对于普通的递归,由于必须要记住递归的调用堆栈,由此产生的耗用是难以估量的。比如下文中php小节第一个例子使用php写一个阶乘函数,就是由于递归造成了栈溢出的错误。尾递归出现的目的就是消除递归栈耗损这个缺憾的。

 

从代码层面看,尾递归其实一句话就可以说清楚了:

函数的最后一个操作是递归调用

 

比如"菲波纳锲"数列的php的递归实现:

1
2
3
4
5
6
7
8
9
10
11
fibonacci.php                                                                                                                        
<?php
function fibonacci($n) {
     if  ($n < 2 ) {
         return  $n;
     }  
     return  fibonacci($n - 1 ) + fibonacci($n - 2 );
}
 
 
var_dump(fibonacci( 30 ));

这是递归函数,但不是尾递归,因为fibonacci的最后一个操作是加法操作。

 

转化为尾递归:

1
2
3
4
5
6
function fibonacci2($n, $acc1, $acc2) {
     if  ($n == 0 ) {
         return  $acc1;
     }  
     return  fibonacci2($n- 1 , $acc2, $acc1 + $acc2);
}

 

fibonacci2就是一个尾递归,它增加两个累加器acc1和acc2,并给出初始的值。记住:递归转化为尾递归的思想一定是增加累加器,减少递归外操作。

更详细的关于写尾递归思路可以参考这篇文章http://runtime.sinaapp.com/?p=32

尾递归的优化方式和原理在老赵的这篇http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html 文章中说得非常非常清楚了,也推荐看看这篇文章

 

尾递归在不同语言上的应用也是不同的。最常使用的就是函数式编程Erlang,几乎是所有出现递归的函数全部都修改成为尾递归。下面说一下尾递归在几个不同的语言上的表现和应用。

php中的尾递归

我们做个实验

普通递归:

1
2
3
4
5
6
7
8
9
10
<?php
function factorial($n)
{
     if ($n == 0 ) {
         return  1 ;
     }  
     return  factorial($n- 1 ) * $n;
}
 
var_dump(factorial( 100000000 ));

尾递归:

1
2
3
4
5
6
7
8
9
10
11
<?php
function factorial($n, $acc)
{
     if ($n == 0 ) {
         return  $acc;
     }  
     return  factorial($n- 1 , $acc * $n);
}
 
 
var_dump(factorial( 100000000 , 1 ));

实验结果:

clip_image001

事实证明,

尾递归在php中是没有任何优化效果的!

在php中尾递归正如老王(http://huoding.com/2012/06/25/158)文章里面说的,并没有任何优化,但是却可以使用Trampoline概念来消除递归,这里也不说了

C中的尾递归

在C中的尾递归优化是gcc编译器做的。在gcc编译的时候加上-O2会对尾递归进行优化

 

我们可以直接看生成的汇编代码:

(使用gdb, gcc –O2 factorial.c –o factorial;    disass factorial)

 

未加-O2生成的汇编:

clip_image002

 

加了O2优化的汇编:

clip_image003

 

不要头大,我也是初看汇编,但是这份代码非常简单,去网上稍微搜搜命令,大致就能理解:

1
2
3
4
5
6
7
8
function factoral(n, sum) {
     while (n != 0 ){
         sum = n * sum
         n = n- 1
     }
     return  sum
 
}

gcc做的确实是智能优化。

 

如果你还有兴趣,你可以使用-O3对尾递归进行优化,并查看其中的汇编指令

-O3的优化是直接将循环展开

 

总结

一般的线性递归修改成为尾递归最大的优势在于减少了递归调用栈的开销。从php那个例子就明显看出来递归开销对程序的影响。但是并不是所有语言都支持尾递归的,即使支持尾递归的语言也一般是在编译阶段对尾递归进行优化,比如上例中的C语言对尾递归的优化。在使用尾递归对代码进行优化的时候,必须先了解这门语言对尾递归的支持。

 

推荐几篇文章

php和lua尾递归:

http://huoding.com/2012/06/25/158

http://www.lua.org/pil/6.3.html

C#的尾递归分析:http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html

C的尾递归分析:

golang的尾递归讨论:

https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/0oIZPHhrDzY





本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/archive/2012/07/12/2588481.html,如需转载请自行联系原作者

相关文章
|
机器学习/深度学习 人工智能 监控
提示词工程深度实践:从基础原理到生产级应用优化
蒋星熠Jaxonic,AI领域技术探索者,深耕提示词工程与大语言模型应用。擅长以系统化思维构建高效人机交互方案,融合认知科学与软件工程,推动AI技术落地。致力于分享提示词设计、架构优化与生产实践,助力开发者提升AI协作能力,在代码星辰中书写极客诗篇。
|
11月前
|
JSON 安全 Java
6.鉴权
本文介绍基于Spring Security与JWT实现客户端Token认证方案,涵盖从登录生成Token到接口验签的完整流程。通过WebSecurityConfig配置安全规则,结合JWTLoginFilter与JWTAuthenticationFilter实现登录验证与请求拦截,利用TokenAuthenticationService完成JWT生成与解析。支持角色与权限控制,构建RBAC权限体系,保障Spring Boot应用安全。
快捷键
【10月更文挑战第19天】快捷键
1085 6
|
安全 Shell 定位技术
抖音修改位置信息怎么改?
抖音虚拟定位技术实现原理与代码实战 一、技术背景与原理分析
|
缓存 算法 物联网
【论文专辑】2024年大模型推理优化论文精选第六期
本文整理了 OSDI 2024 和 SOSP 2024 中与大语言模型(LLM)推理优化相关的10篇论文,涵盖 Parrot、ServerlessLLM、dLoRA 等系统,提出的技术如 Chunked Prefill、Prefix-Caching、P/D分离等已被 vLLM 和 TensorRT-LLM 等主流推理引擎采用。这些研究解决了 LLM 推理中的冷启动延迟、资源分配、KV 缓存管理等问题,提升了推理性能和资源利用率。CodeFuse推理优化项目地址https://github.com/codefuse-ai/EasyDeploy
2393 2
|
机器学习/深度学习 人工智能 自然语言处理
GLM-4V-Flash:智谱 AI 免费开放的图像理解大模型 API 接口
智谱AI推出的GLM-4V-Flash是一款专注于图像理解的免费开放大模型,提供API接口支持用户上传图片URL或Base64编码图片获取详细的图像描述。该模型通过深度学习和卷积神经网络技术,简化了图像分析流程,提高了开发效率,适用于内容审核、辅助视障人士、社交媒体、教育和电子商务等多个应用场景。
12407 14
GLM-4V-Flash:智谱 AI 免费开放的图像理解大模型 API 接口
|
人工智能 程序员 测试技术
AI编程:Coze + Cursor实现一个思维导图的浏览器插件
本文是小卷关于AI编程工具学习的第3篇文章,通过开发一个思维导图生成工具,详细介绍了AI编程的完整流程。从需求分析、插件选择(如Coze的TreeMind),到创建测试工作流、发布API,再到整合API和开发浏览器插件,最终实现了用户选中文字后生成思维导图的功能。文章展示了如何利用现有工具高效开发,并总结了AI编程的优势与未来趋势。
1928 14
|
前端开发
前端使用多张图片生成 Gif 效果(支持循环、不循环、完成回调)
前端使用多张图片生成 Gif 效果(支持循环、不循环、完成回调)
1006 0
|
弹性计算 编解码 大数据
小鹏汽车核心业务迁移至阿里云倚天实例,节省20%算力成本
9月20日,在2024云栖大会上,小鹏汽车宣布车联网、官网、商城、大数据等核心业务已迁移至阿里云倚天实例,节省了超过20%的算力成本。
1075 14
|
移动开发 JavaScript API
Sprunki Game 实现技术分析及介绍
**Sprunki** 是一款基于音乐创作的游戏,作为经典游戏 **Incredibox** 的粉丝改版,它采用 HTML5 和 JavaScript 构建,通过拖拽式 UI 和模块化声音系统,提供了一个创意十足的音乐创作平台。游戏支持多种设备,并融入了 CSS3 动画和 Web Audio API,增强视觉与音效同步。玩家还可以通过社交媒体分享作品,参与社区互动。Sprunki 不仅是一款游戏,更是一个开放的创作平台。