关于尾递归的使用详解


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 相关文章推荐
基于文本的留言簿
Oct 09 PHP
第七节--类的静态成员
Nov 16 PHP
用PHP程序实现支持页面后退的两种方法
Jun 30 PHP
PHP学习之整理字符串
Apr 17 PHP
基于PHP Socket配置以及实例的详细介绍
Jun 13 PHP
php单态设计模式(单例模式)实例
Nov 18 PHP
PHP运用foreach神奇的转换数组(实例讲解)
Feb 01 PHP
php+redis消息队列实现抢购功能
Feb 08 PHP
php关联数组与索引数组及其显示方法
Mar 12 PHP
PHP缓存工具XCache安装与使用方法详解
Apr 09 PHP
PHP设计模式之工厂方法设计模式实例分析
Apr 25 PHP
Laravel框架处理用户的请求操作详解
Dec 20 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连接mysql数据库代码
2009/03/10 PHP
PHP实现的微信公众号扫码模拟登录功能示例
2019/05/30 PHP
Javascript常考语句107条收集
2010/03/09 Javascript
JavaScript高级程序设计 扩展--关于动态原型
2010/11/09 Javascript
解析javascript系统错误:-1072896658的解决办法
2013/07/08 Javascript
IE网页js语法错误2行字符1、FF中正常的解决方法
2013/09/09 Javascript
js简单抽奖代码
2015/01/16 Javascript
初步认识JavaScript函数库jQuery
2015/06/18 Javascript
jQuery 遍历函数详解
2015/07/05 Javascript
简单谈谈javascript Date类型
2015/09/06 Javascript
JavaScript给input的value赋值引发的关于基本类型值和引用类型值问题
2015/12/07 Javascript
学习使用bootstrap3栅格系统
2016/04/12 Javascript
深入理解Ajax的get和post请求
2016/06/02 Javascript
vue将单页面改造成多页面应用的方法
2018/11/25 Javascript
vue.js实现回到顶部动画效果
2019/07/31 Javascript
JavaScript中的类型检查
2020/02/03 Javascript
[03:36]DOTA2完美大师赛coL战队趣味视频——我演你猜
2017/11/23 DOTA
gearman的安装启动及python API使用实例
2014/07/08 Python
Python中typing模块与类型注解的使用方法
2019/08/05 Python
从python读取sql的实例方法
2020/07/21 Python
Python新建项目自动添加介绍和utf-8编码的方法
2020/12/26 Python
通过HTML5 Canvas API绘制弧线和圆形的教程
2016/03/14 HTML / CSS
手把手教你实现一个canvas智绘画板的方法
2019/03/04 HTML / CSS
美国韩国化妆品和护肤品购物网站:Beautytap
2018/07/29 全球购物
德国的大型美妆个护电商:Flaconi
2020/06/26 全球购物
乔迁宴答谢词
2014/01/21 职场文书
群众路线党员个人剖析材料
2014/10/08 职场文书
2014年办公室主任工作总结
2014/11/12 职场文书
离婚协议书范文2014(夫妻感情破裂)
2014/12/14 职场文书
优秀班主任事迹材料
2014/12/16 职场文书
期末复习计划
2015/01/19 职场文书
神农溪导游词
2015/02/11 职场文书
给老婆的保证书怎么写
2015/05/08 职场文书
转学证明范本
2015/06/19 职场文书
2016年优秀团支部事迹材料
2016/02/26 职场文书
SQL Server远程连接的设置步骤(图文)
2022/03/23 SQL Server