关于尾递归的使用详解


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 相关文章推荐
也谈 PHP 和 MYSQL
Oct 09 PHP
PHP文本操作类
Nov 25 PHP
PHP如何解决网站大流量与高并发的问题
Jun 25 PHP
php中让上传的文件大小在上传前就受限制的两种解决方法
Jun 24 PHP
destoon切换城市后实现logo旁边显示地区名称的方法
Aug 21 PHP
PHP图片处理之图片旋转和图片翻转实例
Nov 19 PHP
PHP查询快递信息的方法
Mar 07 PHP
php读取csv文件并输出的方法
Mar 14 PHP
Yii中实现处理前后台登录的新方法
Dec 28 PHP
PHP模块化安装教程
Jun 01 PHP
php compact 通过变量创建数组
Nov 15 PHP
PHP实现时间比较和时间差计算的方法示例
Jul 24 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之第六天
2006/10/09 PHP
深入php常用函数的使用汇总
2013/06/08 PHP
PHP中date与gmdate的区别及默认时区设置
2014/05/12 PHP
php实现图片上传时添加文字和图片水印技巧
2020/04/18 PHP
PHP通过加锁实现并发情况下抢码功能
2016/08/10 PHP
Laravel 框架基于自带的用户系统实现登录注册及错误处理功能分析
2020/04/14 PHP
Js-$.extend扩展方法使方法参数更灵活
2013/01/15 Javascript
javascript获取checkbox复选框获取选中的选项
2014/08/12 Javascript
node.js中的events.emitter.listeners方法使用说明
2014/12/10 Javascript
DOM基础教程之模型中的模型节点
2015/01/19 Javascript
jquery获取及设置outerhtml的方法
2015/03/09 Javascript
ionic 上拉菜单(ActionSheet)实例代码
2016/06/06 Javascript
jquery实用技巧之输入框提示语句
2016/07/28 Javascript
详解angularjs 关于ui-router分层使用
2017/06/12 Javascript
[js高手之路]HTML标签解释成DOM节点的实现方法
2017/08/31 Javascript
解决layui-open关闭自身窗口的问题
2019/09/10 Javascript
简单了解Vue computed属性及watch区别
2020/07/10 Javascript
[09:43]DOTA2每周TOP10 精彩击杀集锦vol.5
2014/06/25 DOTA
Python赋值语句后逗号的作用分析
2015/06/08 Python
Python定时器实例代码
2017/11/01 Python
python opencv之SIFT算法示例
2018/02/24 Python
Python决策树之基于信息增益的特征选择示例
2018/06/25 Python
Python3基于plotly模块保存图片表格
2020/08/03 Python
圣诞树世界:Christmas Tree World
2019/12/10 全球购物
什么是View State?
2013/01/27 面试题
4s客服专员岗位职责
2013/12/01 职场文书
经典安踏广告词
2014/03/21 职场文书
新春文艺演出主持词
2014/03/27 职场文书
课例研修方案
2014/05/31 职场文书
学校学习雷锋活动总结
2014/07/03 职场文书
销售业务员岗位职责
2015/02/13 职场文书
公司行政管理制度范本
2015/08/05 职场文书
Python使用scapy模块发包收包
2021/05/07 Python
Spring Boot DevTools 全局配置学习指南
2022/03/31 Java/Android
什么是Python装饰器?如何定义和使用?
2022/04/11 Python
python运行脚本文件的三种方法实例
2022/06/25 Python