关于尾递归的使用详解_php实例

这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归。
 

尾递归的概念
尾递归(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语言对尾递归的优化。在使用尾递归对代码进行优化的时候,必须先了解这门语言对尾递归的支持。

时间: 2024-12-21 22:37:22

关于尾递归的使用详解_php实例的相关文章

thinkPHP中钩子的两种配置调用方法详解_php实例

本文实例讲述了thinkPHP中钩子的两种配置调用方法.分享给大家供大家参考,具体如下: thinkphp的钩子行为类是一个比较难以理解的问题,网上有很多写thinkphp钩子类的文章,我也是根据网上的文章来设置thinkphp的钩子行为的,但根据这些网上的文章,我在设置的过程中,尝试了十几次都没有成功,不过,我还是没有放弃,最后还是在一边调节细节,一边试验的过程中实现了钩子行为的设置.下面是我个人的设置经验,在这里跟大家分享一下. 个人做了两种设置,都试验成功了,一个简单点,在thinkphp

Zend Framework入门应用实例详解_php实例

本文实例讲述了Zend Framework入门应用.分享给大家供大家参考,具体如下: .htaccess文件 .htaccess文件用来实现URL重置,即当用户访问某资源时,会将其重新定位到指定的文件下. 代码示例: RewriteEngine on RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php 其中,行1表示重置引擎打开,行2表示当访问除js.ico.gif.jpg.png.css以外的文件时, 都将被重置到index.php文件下. 注

Symfony2安装第三方Bundles实例详解_php实例

本文实例讲述了Symfony2安装第三方Bundles的方法.分享给大家供大家参考,具体如下: 大多数的Bundles都提了安装的介绍,下面来介绍基本的安装步骤: 一.添加composer依赖关系 在symfony里,用composer来管理依赖关系 1.找到Bundle的包的名称 在包的README里一般都告诉了我们它的名称,如果没有,可以在https://packagist.org网站里搜索到 2.通过composer来安装Bundle 知道了bundle的包名之后,我们可以通过compos

Symfony2使用第三方库Upload制作图片上传实例详解_php实例

本文实例分析了Symfony2使用第三方库Upload制作图片上传的方法.分享给大家供大家参考,具体如下: 我们在应用程序或者网站的个人资料里一般都有设置头像的功能,这一章我们在Symfony2里用第三方的一个比较有名Upload库来制作上传图片的功能. 一.安装第三方库 1.在composer.json文件中的"require"中加入 "codeguy/upload": "*" 2.运行指令安装 composer update 二.编码 1.编

php中file_exists函数使用详解_php实例

说明:bool file_exists ( string $filename ) 如果由 filename 指定的文件或目录存在则返回 TRUE,否则返回 FALSE. 在Windows上,使用/ /计算机名/共享/文件名或 计算机名共享文件名,以检查网络共享文件. 在 Windows 中要用 //computername/share/filename 或者 \\computername\share\filename 来检查网络中的共享文件. 实例一 <?php $filename = '/jb

PHP时间和日期函数详解_php实例

PHP中所有函数都是UNIX纪元的,即从1970年1月1日开始的. 日期是从这个时候开始的秒数. 当一个函数调用从这时候计的秒数时,就把它当作(timestamp)时间戳. 本地时间函数 1. string date(string format,inieger timestamp) 该函数返回一个表示时间的字符串,是由string format 控制的. 如: <? print(date("Y年 m月d日");//输出当前,年月日. print(date("Y年 m月d

php函数重载的替代方法--伪重载详解_php实例

函数重载的替代方法-伪重载,下面看一个具体的实例代码. <? php //函数重载的替代方法-伪重载 // //确实,在PHP中没有函数重载这个概念,让很多时候我们无法进行一些处理,甚至有时候不得不在函数后面定义好N个参数 //在看到了func_get_arg,func_get_args,func_num_args,这三个函数的时候,你们是不是想起了什么? function testOne ( $a ) { echo (' 一个参数就这样 '); } function testTwo ( $a

ThinkPHP模板比较标签用法详解_php实例

ThinkPHP模板引擎提供了丰富的比较标签,其用法格式为: <比较标签 name="变量" value="值">内容</比较标签> ThinkPHP系统支持的比较标签及其所表示的含义分别是: eq或者 equal:等于 neq 或者notequal:不等于 gt:大于 egt:大于等于 lt:小于 elt:小于等于 heq:恒等于 nheq:不恒等于 1.比较标签的用法基本是一致的,区别在于判断的条件不同. 如eq标签: <eq na

Yii CGridView用法实例详解_php实例

本文实例讲述了Yii CGridView用法.分享给大家供大家参考,具体如下: CGridView的功能是用来显示的数据列表.它支持排序,分页,和AJAX数据请求. CGridView最好使用 data provider,最好是 CActiveDataProvider . 简单代码如下: $dataProvider=new CActiveDataProvider('Post'); $this->widget('zii.widgets.grid.CGridView', array( 'dataPr