关于尾递归的使用详解


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
PHP4 与 MySQL 数据库操作函数详解
Oct 09 PHP
在字符串指定位置插入一段字符串的php代码
Feb 16 PHP
数据库中排序的对比及使用条件详解
Feb 23 PHP
PHP中使用foreach和引用导致程序BUG的问题介绍
Sep 05 PHP
php获取$_POST同名参数数组的实现介绍
Jun 30 PHP
php树型类实例
Dec 05 PHP
PHP消息队列用法实例分析
Feb 12 PHP
session 加入redis的实现代码
Jul 15 PHP
php 基础函数
Feb 10 PHP
PHP实现找出链表中环的入口节点
Jan 16 PHP
PHP 观察者模式深入理解与应用分析
Sep 25 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查询数据库中满足条件的记录条数(两种实现方法)
2013/01/29 PHP
基于PHP遍历数组的方法汇总分析
2013/06/08 PHP
基于GD2图形库的PHP生成图片缩略图类代码分享
2015/02/08 PHP
详解PHP函数 strip_tags 处理字符串缺陷bug
2017/06/11 PHP
JavaScript中的连字符详解
2013/11/28 Javascript
一段非常简单的js判断浏览器的内核
2014/08/17 Javascript
使用AmplifyJS组件配合JavaScript进行编程的指南
2015/07/28 Javascript
使用 stylelint检查CSS_StyleLint
2016/04/28 Javascript
jquery 校验中国身份证号码实例详解
2017/04/11 jQuery
详解Vue2.X的路由管理记录之 钩子函数(切割流水线)
2017/05/02 Javascript
5分钟打造简易高效的webpack常用配置
2017/07/04 Javascript
浅析Node.js非对称加密方法
2018/01/29 Javascript
详解Puppeteer前端自动化测试实践
2019/02/21 Javascript
AngularJs中$cookies简单用法分析
2019/05/30 Javascript
Node.JS枚举统计当前文件夹和子目录下所有代码文件行数
2019/08/23 Javascript
解决layer.open后laydate失效的问题
2019/09/06 Javascript
解决layer弹出层msg的文字不显示的问题
2019/09/11 Javascript
Vue路由守卫之路由独享守卫
2019/09/25 Javascript
[56:00]2018DOTA2亚洲邀请赛 4.6 淘汰赛 VP vs TNC 第二场
2018/04/10 DOTA
跟老齐学Python之大话题小函数(2)
2014/10/10 Python
一个基于flask的web应用诞生(1)
2017/04/11 Python
详解flask入门模板引擎
2018/07/18 Python
对python插入数据库和生成插入sql的示例讲解
2018/11/14 Python
python定时复制远程文件夹中所有文件
2019/04/30 Python
Python中print和return的作用及区别解析
2019/05/05 Python
对Python中画图时候的线类型详解
2019/07/07 Python
tensorflow 获取所有variable或tensor的name示例
2020/01/04 Python
PyQt5-QDateEdit的简单使用操作
2020/07/12 Python
一篇文章搞懂python的转义字符及用法
2020/09/03 Python
python实现跨年表白神器--你值得拥有
2021/01/04 Python
实验教师岗位职责
2014/02/13 职场文书
财务总监岗位职责
2014/03/07 职场文书
2014年教师党员公开承诺书
2014/05/28 职场文书
详解Python为什么不用设计模式
2021/06/24 Python
mysql函数之截取字符串的实现
2022/08/14 MySQL