关于尾递归的使用详解


Posted in PHP onMay 02, 2013

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

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

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

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

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

fibonacci.php                                                                                                                          
<?php 
function fibonacci($n) { 
    if ($n < 2) { 
        return $n;  
    }    
    return fibonacci($n - 1) + fibonacci($n - 2);  
}   
var_dump(fibonacci(30));

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

转化为尾递归:

function fibonacci2($n, $acc1, $acc2) { 
    if ($n == 0) { 
        return $acc1; 
    }    
    return fibonacci2($n-1, $acc2, $acc1 + $acc2); 
}

fibonacci2就是一个尾递归,它增加两个累加器acc1和acc2,并给出初始的值。记住:递归转化为尾递归的思想一定是增加累加器,减少递归外操作。
尾递归在不同语言上的应用也是不同的。最常使用的就是函数式编程Erlang,几乎是所有出现递归的函数全部都修改成为尾递归。下面说一下尾递归在几个不同的语言上的表现和应用。

php中的尾递归
我们做个实验

普通递归:

<?php 
function factorial($n) 
{ 
    if($n == 0) { 
        return 1; 
    }    
    return factorial($n-1) * $n;  
} var_dump(factorial(100000000));

尾递归:
<?php
function factorial($n, $acc)
{
    if($n == 0) {
        return $acc;
    }  
    return factorial($n-1, $acc * $n);
} 
var_dump(factorial(100000000, 1));

实验结果:

关于尾递归的使用详解

事实证明,
尾递归在php中是没有任何优化效果的!

C中的尾递归

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

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

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

未加-O2生成的汇编:
关于尾递归的使用详解
加了O2优化的汇编:
关于尾递归的使用详解
不要头大,我也是初看汇编,但是这份代码非常简单,去网上稍微搜搜命令,大致就能理解:

function factoral(n, sum) {
    while(n != 0){
        sum = n * sum
        n = n-1
    }
    return sum}

gcc做的确实是智能优化。

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

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

总结

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

PHP 相关文章推荐
对squid中refresh_pattern的一些理解和建议
Apr 17 PHP
发款php蜘蛛统计插件只要有mysql就可用
Oct 12 PHP
php漏洞之跨网站请求伪造与防止伪造方法
Aug 15 PHP
Laravel 5 学习笔记
Mar 06 PHP
PHP的pcntl多进程用法实例
Mar 19 PHP
php给每个段落添加空格的方法
Mar 20 PHP
PHP自动生成表单代码分享
Jun 19 PHP
Zend Framework教程之模型Model用法简单实例
Mar 04 PHP
PHP基于反射机制实现插件的可插拔设计详解
Nov 10 PHP
php实现的简单数据库操作Model类
Nov 16 PHP
php利用ob_start()清除输出和选择性输出的方法
Jan 18 PHP
php引用传递
Apr 01 PHP
基于Zend的Config机制的应用分析
May 02 #PHP
Zend的Registry机制的使用说明
May 02 #PHP
Zend的MVC机制使用分析(二)
May 02 #PHP
Zend的MVC机制使用分析(一)
May 02 #PHP
基于Zend的Captcha机制的应用
May 02 #PHP
PHP静态调用非静态方法的应用分析
May 02 #PHP
Mysql中分页查询的两个解决方法比较
May 02 #PHP
You might like
php计算2个日期的差值函数分享
2015/02/02 PHP
关于PHP开发的9条建议
2015/07/27 PHP
PHP实现的激活用户注册验证邮箱功能示例
2017/06/06 PHP
php+ajax实现无刷新文件上传功能(ajaxuploadfile)
2018/02/11 PHP
php使用QueryList轻松采集js动态渲染页面方法
2018/09/11 PHP
用JavaScript编写COM组件的步骤
2009/03/17 Javascript
ExtJS4 Grid改变单元格背景颜色及Column render学习
2013/02/06 Javascript
jquery中文乱码的多种解决方法
2013/06/21 Javascript
jQuery实现两款有动画功能的导航菜单代码
2015/09/16 Javascript
Javascript基于对象三大特性(封装性、继承性、多态性)
2016/01/04 Javascript
Javascript中Date类型和Math类型详解
2016/02/27 Javascript
jQuery原理系列-css选择器的简单实现
2016/06/07 Javascript
Vue 解决路由过渡动画抖动问题(实例详解)
2020/01/05 Javascript
如何在 ant 的table中实现图片的渲染操作
2020/10/28 Javascript
vue项目配置 webpack-obfuscator 进行代码加密混淆的实现
2021/02/26 Vue.js
[01:10]3.19DOTA2发布会 三代刀塔人第一代
2014/03/25 DOTA
浅谈Python中的作用域规则和闭包
2018/03/20 Python
python算法与数据结构之冒泡排序实例详解
2019/06/22 Python
python开发之anaconda以及win7下安装gensim的方法
2019/07/05 Python
Python下利用BeautifulSoup解析HTML的实现
2020/01/17 Python
找Python安装目录,设置环境路径以及在命令行运行python脚本实例
2020/03/09 Python
keras实现调用自己训练的模型,并去掉全连接层
2020/06/09 Python
详解canvas多边形(蜘蛛图)的画法示例
2018/01/29 HTML / CSS
美国男装连锁零售商:Men’s Wearhouse
2016/10/14 全球购物
草莓网美国官网:Strawberrynet USA
2016/12/11 全球购物
制药工程专业毕业生推荐信
2013/12/24 职场文书
销售人员职业生涯规划范文
2014/03/01 职场文书
教导主任竞聘演讲稿
2014/05/16 职场文书
法定代表人资格证明书
2014/09/11 职场文书
2014年乡镇妇联工作总结
2014/12/02 职场文书
2014年幼儿园老师工作总结
2014/12/05 职场文书
2014年机关后勤工作总结
2014/12/16 职场文书
小学语文教师研修感悟
2015/11/18 职场文书
世界文化遗产导游词
2019/08/07 职场文书
CSS3通过var()和calc()函数实现动画特效
2021/03/30 HTML / CSS
MySQL实例精讲单行函数以及字符数学日期流程控制
2021/10/15 MySQL