关于尾递归的使用详解


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 date函数参数详解
Nov 27 PHP
php header示例代码(推荐)
Sep 08 PHP
将时间以距今多久的形式表示,PHP,js双版本
Sep 25 PHP
解析PHP高效率写法(详解原因)
Jun 20 PHP
基于PHP文件操作的详细诠释
Jun 21 PHP
php发送http请求的常用方法分析
Nov 08 PHP
php验证码生成器
May 24 PHP
PHP大文件分割上传 PHP分片上传
Aug 28 PHP
OAuth认证协议中的HMACSHA1加密算法(实例)
Oct 25 PHP
Laravel实现短信注册的示例代码
May 29 PHP
PHP文件操作实例总结【文件上传、下载、分页】
Dec 08 PHP
Thinkphp5.0框架视图view的循环标签用法示例
Oct 12 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
为了这两部电子管收音机,买了6套全新电子管和10粒刻度盘灯泡
2021/03/02 无线电
微信扫描二维码登录网站代码示例
2013/12/30 PHP
php实现的CSS更新类实例
2014/09/22 PHP
php析构函数的简单使用说明
2015/08/24 PHP
php判断邮箱地址是否存在的方法
2016/02/13 PHP
laravel 实现设置时区的简单方法
2019/10/10 PHP
用JavaScript实现单继承和多继承的简单方法
2009/03/29 Javascript
基于jquery实现的类似百度搜索的输入框自动完成功能
2011/08/23 Javascript
解析javascript 数组以及json元素的添加删除
2013/06/26 Javascript
解析jquery中的ajax缓存问题
2013/12/19 Javascript
jQuery easyUI datagrid 增加求和统计行的实现代码
2016/06/01 Javascript
利用JQuery阻止事件冒泡
2016/12/01 Javascript
Angularjs中的ui-bootstrap的使用教程
2017/02/19 Javascript
解决vue中使用swiper插件问题及swiper在vue中的用法
2018/04/04 Javascript
Vue项目全局配置页面缓存之按需读取缓存的实现详解
2018/08/01 Javascript
vue+canvas实现炫酷时钟效果的倒计时插件(已发布到npm的vue2插件,开箱即用)
2018/11/05 Javascript
详解关于html,css,js三者的加载顺序问题
2019/04/10 Javascript
angular使用md5,CryptoJS des加密的方法
2019/06/03 Javascript
node.js使用http模块创建服务器和客户端完整示例
2020/02/10 Javascript
15个简单的JS编码标准让你的代码更整洁(小结)
2020/07/16 Javascript
如何区分vue中的v-show 与 v-if
2020/09/08 Javascript
vue $mount 和 el的区别说明
2020/09/11 Javascript
js前端对于大量数据的展示方式及处理方法
2020/12/02 Javascript
基于Django用户认证系统详解
2018/02/21 Python
Flask框架WTForm表单用法示例
2018/07/20 Python
使用OpenCV实现仿射变换—平移功能
2019/08/29 Python
Django自定义列表 models字段显示方式
2020/04/03 Python
python math模块的基本使用教程
2021/01/16 Python
详解HTML5 canvas绘图基本使用方法
2018/01/29 HTML / CSS
HTML5教程之html 5 本地数据库(Web Sql Database)
2014/04/03 HTML / CSS
HTML5新增form控件和表单属性实例代码详解
2019/05/15 HTML / CSS
小学语文教学反思
2014/02/10 职场文书
仓库规划计划书
2014/04/28 职场文书
个人作风纪律整顿整改措施
2014/10/25 职场文书
医者仁心观后感
2015/06/17 职场文书
Python编写可视化界面的全过程(Python+PyCharm+PyQt)
2021/05/17 Python