开发者社区> 技术mix呢> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

又见尾递归

简介:
+关注继续查看

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

 

尾递归的概念

尾递归(Tail Recursion)的概念是递归概念的一个子集。对于普通的递归,由于必须要记住递归的调用堆栈,由此产生的耗用是难以估量的。比如下文中php小节第一个例子使用php写一个阶乘函数,就是由于递归造成了栈溢出的错误。尾递归出现的目的就是消除递归栈耗损这个缺憾的。

 

从代码层面看,尾递归其实一句话就可以说清楚了:

函数的最后一个操作是递归调用

 

比如"菲波纳锲"数列的php的递归实现:

1
2
3
4
5
6
7
8
9
10
11
fibonacci.php                                                                                                                        
<?php
function fibonacci($n) {
    if ($n < 2) {
        return $n;
    }  
    return fibonacci($n - 1) + fibonacci($n - 2);
}
 
 
var_dump(fibonacci(30));

这是递归函数,但不是尾递归,因为fibonacci的最后一个操作是加法操作。

 

转化为尾递归:

1
2
3
4
5
6
function fibonacci2($n, $acc1, $acc2) {
    if ($n == 0) {
        return $acc1;
    }  
    return fibonacci2($n-1, $acc2, $acc1 + $acc2);
}

 

fibonacci2就是一个尾递归,它增加两个累加器acc1和acc2,并给出初始的值。记住:递归转化为尾递归的思想一定是增加累加器,减少递归外操作。

更详细的关于写尾递归思路可以参考这篇文章http://runtime.sinaapp.com/?p=32

尾递归的优化方式和原理在老赵的这篇http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html 文章中说得非常非常清楚了,也推荐看看这篇文章

 

尾递归在不同语言上的应用也是不同的。最常使用的就是函数式编程Erlang,几乎是所有出现递归的函数全部都修改成为尾递归。下面说一下尾递归在几个不同的语言上的表现和应用。

php中的尾递归

我们做个实验

普通递归:

1
2
3
4
5
6
7
8
9
10
<?php
function factorial($n)
{
    if($n == 0) {
        return 1;
    }  
    return factorial($n-1) * $n;
}
 
var_dump(factorial(100000000));

尾递归:

1
2
3
4
5
6
7
8
9
10
11
<?php
function factorial($n, $acc)
{
    if($n == 0) {
        return $acc;
    }  
    return factorial($n-1, $acc * $n);
}
 
 
var_dump(factorial(100000000, 1));

实验结果:

clip_image001

事实证明,

尾递归在php中是没有任何优化效果的!

在php中尾递归正如老王(http://huoding.com/2012/06/25/158)文章里面说的,并没有任何优化,但是却可以使用Trampoline概念来消除递归,这里也不说了

C中的尾递归

在C中的尾递归优化是gcc编译器做的。在gcc编译的时候加上-O2会对尾递归进行优化

 

我们可以直接看生成的汇编代码:

(使用gdb, gcc –O2 factorial.c –o factorial;    disass factorial)

 

未加-O2生成的汇编:

clip_image002

 

加了O2优化的汇编:

clip_image003

 

不要头大,我也是初看汇编,但是这份代码非常简单,去网上稍微搜搜命令,大致就能理解:

1
2
3
4
5
6
7
8
function factoral(n, sum) {
    while(n != 0){
        sum = n * sum
        n = n-1
    }
    return sum
 
}

gcc做的确实是智能优化。

 

如果你还有兴趣,你可以使用-O3对尾递归进行优化,并查看其中的汇编指令

-O3的优化是直接将循环展开

 

总结

一般的线性递归修改成为尾递归最大的优势在于减少了递归调用栈的开销。从php那个例子就明显看出来递归开销对程序的影响。但是并不是所有语言都支持尾递归的,即使支持尾递归的语言也一般是在编译阶段对尾递归进行优化,比如上例中的C语言对尾递归的优化。在使用尾递归对代码进行优化的时候,必须先了解这门语言对尾递归的支持。

 

推荐几篇文章

php和lua尾递归:

http://huoding.com/2012/06/25/158

http://www.lua.org/pil/6.3.html

C#的尾递归分析:http://www.cnblogs.com/JeffreyZhao/archive/2009/04/01/tail-recursion-explanation.html

C的尾递归分析:

golang的尾递归讨论:

https://groups.google.com/forum/?fromgroups#!topic/golang-nuts/0oIZPHhrDzY





本文转自轩脉刃博客园博客,原文链接:http://www.cnblogs.com/yjf512/archive/2012/07/12/2588481.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
汉诺塔游戏与递归
汉诺塔游戏与递归
24 0
快速幂----递归
快速幂----递归
41 0
递归小记
    来自实际遇到的一个问题,需要查找出根节点的下的所有子节点,首先想到的就是递归了,用JS写过,C#之前写了一次没写对,这次专门用心看了一下的,发现和ref关键字有关,写贴上源码:      public static void InsertCmsTypeName(int typeid,...
1004 0
递归
1、         递归跟算法时间复杂度没什么关系 2、         assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息 最近有点忙,以后补 ...
437 0
Kotlin尾递归优化
一、尾递归优化 1.递归的一种特殊形式 2.调用自身后无其他的操作 3.tailrec关键字提示编译器尾递归优化 二、具体的来看看一下代码说明 package net.
1040 0
+关注
2968
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载