关于尾递归的使用详解


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与浏览器缓存的分析
Jun 03 PHP
php中有关合并某一字段键值相同的数组合并的改进
Mar 10 PHP
PHP文件操作方法汇总
Jul 01 PHP
PHP中的Session对象如何使用
Sep 25 PHP
PHP+MySQL统计该库中每个表的记录数并按递减顺序排列的方法
Feb 15 PHP
php实现转换html格式为文本格式的方法
May 16 PHP
PHP全功能无变形图片裁剪操作类与用法示例
Jan 10 PHP
轻松实现php文件上传功能
Feb 17 PHP
thinkphp5 加载静态资源路径与常量的方法
Dec 24 PHP
php接口实现拖拽排序功能
Apr 23 PHP
PHP的mysqli_rollback()函数讲解
Jan 23 PHP
Laravel使用Queue队列的技巧汇总
Sep 02 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 基于文件头的文件类型验证类函数
2012/05/01 PHP
探讨PHP使用eAccelerator的API开发详解
2013/06/09 PHP
在php和MySql中计算时间差的方法详解
2015/03/27 PHP
写了10年的Javascript也未必全了解的连续赋值运算
2011/03/25 Javascript
JavaScript在for循环中绑定事件解决事件参数不同的情况
2014/01/20 Javascript
javascript原生和jquery库实现iframe自适应高度和宽度
2014/07/18 Javascript
javascript折半查找详解
2015/01/26 Javascript
JS实现的4种数字千位符格式化方法分享
2015/03/02 Javascript
jQuery+AJAX实现无刷新下拉加载更多
2015/07/03 Javascript
jQuery用FormData实现文件上传的方法
2016/11/21 Javascript
js编写选项卡效果
2017/05/23 Javascript
js原生代码实现轮播图的实例讲解
2017/07/28 Javascript
Javascript 实现 Excel 导入生成图表功能
2018/10/22 Javascript
详解微信小程序缓存--缓存时效性
2019/05/02 Javascript
layui时间控件选择时间范围的实现方法
2019/09/28 Javascript
微信小程序实现上传多张图片、删除图片
2020/07/29 Javascript
python绘图方法实例入门
2015/05/19 Python
使用Python的Flask框架表单插件Flask-WTF实现Web登录验证
2016/07/12 Python
Python实现购物系统(示例讲解)
2017/09/13 Python
python3读取csv和xlsx文件的实例
2018/06/22 Python
Python字符串逆序输出的实例讲解
2019/02/16 Python
Python中的 ansible 动态Inventory 脚本
2020/01/19 Python
python GUI库图形界面开发之PyQt5布局控件QHBoxLayout详细使用方法与实例
2020/03/06 Python
CSS3实现超慢速移动动画效果非常流畅无卡顿
2014/06/15 HTML / CSS
html5简介_动力节点Java学院整理
2017/07/07 HTML / CSS
意大利消费电子产品购物网站:SLG Store
2019/12/26 全球购物
中间件分为哪几类
2016/09/18 面试题
夜大毕业生自我鉴定
2013/10/31 职场文书
党性观念心得体会
2014/09/03 职场文书
表扬稿格式范文
2015/01/16 职场文书
故宫导游词
2015/01/31 职场文书
复兴之路展览观后感
2015/06/02 职场文书
惊天动地观后感
2015/06/10 职场文书
大学军训口号大全
2015/12/24 职场文书
MySQL索引篇之千万级数据实战测试
2021/04/05 MySQL