关于尾递归的使用详解


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 XMLWriter类的简单示例代码(RSS输出)
Sep 30 PHP
提高PHP编程效率的方法
Nov 07 PHP
PHP开发工具ZendStudio下Xdebug工具使用说明详解
Nov 11 PHP
php的array数组和使用实例简明教程(容易理解)
Mar 20 PHP
Linux中用PHP判断程序运行状态的2个方法
May 04 PHP
Yii实现多数据库主从读写分离的方法
Dec 29 PHP
PHP获取数组长度或某个值出现次数的方法
Feb 11 PHP
一个非常完美的读写ini格式的PHP配置类分享
Feb 12 PHP
Yii控制器中filter过滤器用法分析
Jul 15 PHP
Django中通过定时任务触发页面静态化的处理方式
Aug 29 PHP
tp5框架的增删改查操作示例
Oct 31 PHP
一文搞懂php的垃圾回收机制
Jun 18 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邮件发送,php发送邮件的类
2011/03/24 PHP
PHP mysql与mysqli事务使用说明 分享
2013/08/17 PHP
json的键名为数字时的调用方式(示例代码)
2013/11/15 PHP
PHP使用array_fill定义多维数组的方法
2015/03/18 PHP
用JQuery 实现AJAX加载XML并解析的脚本
2009/07/25 Javascript
javascript获取当前日期时间及其它操作函数
2011/01/11 Javascript
jquery实现table鼠标经过变色代码
2013/09/25 Javascript
Javascript验证用户输入URL地址是否为空及格式是否正确
2014/10/09 Javascript
JavaScript框架是什么?怎样才能叫做框架?
2015/07/01 Javascript
JS密码生成与强度检测完整实例(附demo源码下载)
2016/04/06 Javascript
javascript之Boolean类型对象
2016/06/07 Javascript
完美JQuery图片切换效果的简单实现
2016/07/21 Javascript
jQuery Easyui DataGrid点击某个单元格即进入编辑状态焦点移开后保存数据
2016/08/15 Javascript
ReactNative-JS 调用原生方法实例代码
2016/10/08 Javascript
Bootstrap整体框架之JavaScript插件架构
2016/12/15 Javascript
Vue添加请求拦截器及vue-resource 拦截器使用
2017/11/23 Javascript
使用async、enterproxy控制并发数量的方法详解
2018/01/02 Javascript
js+canvas实现两张图片合并成一张图片的方法
2019/11/01 Javascript
[01:21:07]EG vs Liquid 2018国际邀请赛淘汰赛BO3 第一场 8.25
2018/08/29 DOTA
Python计算程序运行时间的方法
2014/12/13 Python
python实现一次创建多级目录的方法
2015/05/15 Python
Python中的with...as用法介绍
2015/05/28 Python
Python 解析pymysql模块操作数据库的方法
2020/02/18 Python
150行Python代码实现带界面的数独游戏
2020/04/04 Python
字符串str除首尾字符外的其他字符按升序排列
2013/03/08 面试题
毕业生动漫设计求职信
2013/10/11 职场文书
企划主管岗位职责
2013/12/12 职场文书
旅游管理专业大学生职业规划书
2014/02/27 职场文书
岗位廉洁从业承诺书
2014/03/28 职场文书
2014年五四青年节活动策划书
2014/04/22 职场文书
企业诚信承诺书
2014/05/23 职场文书
学校运动会广播稿100条
2014/09/14 职场文书
七一表彰大会简报
2015/07/20 职场文书
小学信息技术教学反思
2016/02/16 职场文书
学前班教学反思
2016/02/24 职场文书
Python使用pandas导入csv文件内容的示例代码
2022/12/24 Python