关于尾递归的使用详解 |
本文标签:尾递归 这几天看到几篇关于尾递归的文章,之前对尾递归没有多大概念,所以回头研究了一下尾递归 。 尾递归的概念
函数的最后一个操作是递归调用 比如"菲波纳锲"数列的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)); 实验结果: 事实证明, C中的尾递归 在C中的尾递归优化是gcc编译器做的 。在gcc编译的时候加上-O2会对尾递归进行优化
(使用gdb, gcc –O2 factorial.c –o factorial; disass factorial) 未加-O2生成的汇编: 复制代码 代码如下: function factoral(n, sum) { while(n != 0){ sum = n * sum n = n-1 } return sum } gcc做的确实是智能优化 。
-O3的优化是直接将循环展开
一般的线性递归修改成为尾递归最大的优势在于减少了递归调用栈的开销 。从php那个例子就明显看出来递归开销对程序的影响 。但是并不是所有语言都支持尾递归的,即使支持尾递归的语言也一般是在编译阶段对尾递归进行优化,比如上例中的C语言对尾递归的优化 。在使用尾递归对代码进行优化的时候,必须先了解这门语言对尾递归的支持 。 |