发表时间:2015-05-27来源:网络
在程序设计中,递归(Recursion)是一个很常见的概念,合理使用递归,可以提升代码的可读性,但同时也可能会带来一些问题。
下面以阶乘(Factorial)为例来说明一下递归的用法,实现语言是PHP:
<?php
function factorial($n) {
if ($n == 0) {
return 1;
}
return factorial($n - 1) * $n;
}
var_dump(factorial(100));
?>
如果安装了XDebug的话,可能会遇到如下错误:
Fatal error: Maximum function nesting level of ’100′ reached, aborting!
注:这是XDebug的一个保护机制,可以通过max_nesting_level选项来设置。
即便代码能正常运行,只要我们不断增大参数,程序迟早会报错:
Fatal error: Allowed memory size of … bytes exhausted
为什么呢?简单点说就是递归造成了栈溢出。有几个方法可以用来规避这个问题,比如说利用尾调用(Tail Call)来消除递归对栈的影响。
下面以Lua作为描述语言来说明尾调用的含义,代码如下:
function factorial(n)
if (n == 0) then
return 1
end
return factorial(n - 1) * n
end
print(factorial(100))
这段代码同样会遇到栈溢出的问题。怎样利用尾调用来搞定呢?让我们先来看看尾调用的定义:如果一个函数在执行了一次函数调用后,不再做别的事就称为尾调用。形象点说就是直接返回一个函数调用。尾调用不会返回原来的函数,所以不需要额外的栈保留调用函数的数据。上面代码改成尾调用后类似下面代码的样子:
function factorial(n, accumulator)
accumulator = accumulator or 1
if (n == 0) then
return accumulator
end
return factorial(n - 1, accumulator * n)
end
print(factorial(100))
注:关于Lua中尾调用的介绍可以参考:Proper Tail Recursion。
照猫画虎,我们用PHP来实现一个尾调用版本的阶乘:
<?php
function factorial($n, $accumulator = 1) {
if ($n == 0) {
return $accumulator;
}
return factorial($n - 1, $accumulator * $n);
}
var_dump(factorial(100));
?>
可惜测试后才发现PHP根本不支持尾调用!好在天无绝人之路,仔细阅读维基百科中关于尾调用的介绍,你会发现里面提到了Trampoline的概念。简单点说就是利用高阶函数消除递归,依照这样的理论基础,我们可以把上面的尾调用代码改写成如下方式:
<?php
function factorial($n, $accumulator = 1) {
if ($n == 0) {
return $accumulator;
}
return function() use($n, $accumulator) {
return factorial($n - 1, $accumulator * $n);
};
}
function trampoline($callback, $params) {
$result = call_user_func_array($callback, $params);
while (is_callable($result)) {
$result = $result();
}
return $result;
}
var_dump(trampoline('factorial', array(100)));
?>
看上去不错,不过我不得不向大家道个歉,本文用递归实现阶乘其实是个玩笑,实际上只要用一个循环就行了,《代码大全》里专门提到了这一点:
<?php
function factorial($n) {
$result = 1;
for ($i = 1; $i <= $n; $i++) {
$result *= $i;
}
return $result;
}
var_dump(factorial(100));
?>
还有很多别的方法可以用来规避递归引起的栈溢出问题,比如说Python中可以通过装饰器和异常来消灭尾调用,让人有一种别有洞天的感觉:
Tail Call Optimization Decorator (Python recipe)
另外,Python之父关于为何不在Python中支持尾调用的博文也很有看头:
Tail Recursion Elimination
Final Words on Tail Calls
好了,就写到这吧。除非能提升代码可读性,否则没有必要使用递归;迫不得已之时,最好考虑使用Tail Call或Trampoline等技术来规避潜在的栈溢出问题。
上一篇:qeephp内容分页
下一篇:数据库主从设置
CI框架连接数据库配置操作以及多数据库操作
asp 简单读取数据表并列出来 ASP如何快速从数据库读取大量数据
C语言关键字及其解释介绍 C语言32个关键字详解
C语言中sizeof是什么意思 c语言里sizeof怎样用法详解
PHP中的魔术方法 :__construct, __destruct , __call, __callStatic,__get, __set, __isset, __unset , __sleep,
将视频设置为Android手机开机动画的教程
PHP中的(++i)前缀自增 和 (i++)后缀自增
最简单的asp登陆界面代码 asp登陆界面源代码详细介绍
常用dos命令及语法
PHP中include和require区别之我见
康养洪雅客户端下载v4.7.4 安卓最新版
57.33MB |资讯阅读
掌中聊城手机客户端(更名新聊城)下载v8.0.2 安卓官方版
39.84MB |资讯阅读
书香金溪客户端下载v3.1.2 安卓版
77.76MB |资讯阅读
靖安发布新闻客户端下载v3.1.2 安卓最新版
77.69M |资讯阅读
115管理app下载v36.0.3 安卓版
76.82MB |商务办公
任城融媒体中心下载v2.0.19 安卓官方版
97.83M |资讯阅读
亨特物流港最新版下载v3.1.4 安卓版
17.55MB |生活服务
建筑工匠客户端下载v7.6.3 安卓版
82.32MB |学习教育
2014-09-05
2022-03-20
2022-03-21
2022-03-24
2014-09-05
2014-09-05
2015-07-05
2014-09-05
2022-03-21
2014-09-05