关于尾递归的使用详解


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 数组的合并、拆分、区别取值函数集
Feb 15 PHP
PHP print类函数使用总结
Jun 25 PHP
PHP Stream_*系列函数
Aug 01 PHP
浅析PHP substr,mb_substr以及mb_strcut的区别和用法
Jun 21 PHP
查找php配置文件php.ini所在路径的二种方法
May 26 PHP
Codeigniter框架的更新事务(transaction)BUG及解决方法
Jul 25 PHP
html静态页面调用php文件的方法
Nov 13 PHP
php和editplus正则表达式去除空白行
Apr 17 PHP
PHP实现GIF图片验证码
Nov 04 PHP
PHP房贷计算器实例代码,等额本息,等额本金
Apr 01 PHP
CI框架实现创建自定义类库的方法
Dec 25 PHP
yii 框架实现按天,月,年,自定义时间段统计数据的方法分析
Apr 04 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
防止MySQL注入或HTML表单滥用的PHP程序
2009/01/21 PHP
PHP字符编码问题之GB2312 VS UTF-8解决方法
2011/06/23 PHP
php实现插入数组但不影响原有顺序的方法
2015/03/27 PHP
php 批量查询搜狗sogou代码分享
2015/05/17 PHP
PHP pear安装配置教程
2016/05/14 PHP
php利用云片网实现短信验证码功能的示例代码
2017/11/18 PHP
PHP实现转盘抽奖算法分享
2020/04/15 PHP
JScript中的&quot;this&quot;关键字使用方式补充材料
2007/03/08 Javascript
YUI的Tab切换实现代码
2010/04/11 Javascript
js异常捕获方法介绍
2013/04/10 Javascript
jQuery的slideToggle方法实例
2013/05/07 Javascript
MyEclipse取消验证Js的两种方法
2013/11/14 Javascript
通过正则表达式实现表单验证是否为中文
2014/02/18 Javascript
javascript制作sql转换为stringBuffer的小工具
2015/04/03 Javascript
JQuery+Ajax实现数据查询、排序和分页功能
2015/09/27 Javascript
vue的Virtual Dom实现snabbdom解密
2017/05/03 Javascript
关于javascript作用域的常见面试题分享
2017/06/18 Javascript
利用JS制作万年历的方法
2017/08/16 Javascript
关于js的三种使用方式(行内js、内部js、外部js)的程序代码
2018/05/05 Javascript
详解vue后台系统登录态管理
2019/04/02 Javascript
[10:04]国际邀请赛采访专栏:DK.Farseer,mouz.Black^,采访员Josh专访
2013/08/05 DOTA
Python中多线程及程序锁浅析
2015/01/21 Python
浅析Python中的多重继承
2015/04/28 Python
python实战教程之自动扫雷
2018/07/13 Python
使用Pycharm在运行过程中,查看每个变量的操作(show variables)
2020/06/08 Python
python软件都是免费的吗
2020/06/18 Python
Python性能分析工具py-spy原理用法解析
2020/07/27 Python
详解HTML5将footer置于页面最底部的方法(CSS+JS)
2018/10/11 HTML / CSS
基层干部2014全国两会学习心得体会
2014/03/10 职场文书
毕业留言寄语大全
2014/04/10 职场文书
八一建军节营销活动方案
2014/08/31 职场文书
Python pandas之求和运算和非空值个数统计
2021/08/07 Python
NGINX 权限控制文件预览和下载的实现原理
2022/01/18 Servers
基于docker安装zabbix的详细教程
2022/06/05 Servers
MySQL的意向共享锁、意向排它锁和死锁
2022/07/15 MySQL