在程序设计中,递归(Recursion)是一个很常见的概念,合理使用递归,可以提升代码的可读性,但同时也可能会带来一些问题。

下面以阶乘(Factorial)为例来说明一下递归的用法,实现语言是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来实现一个尾调用版本的阶乘:

function factorial($n, $accumulator = 1) {

if ($n == 0) {

return $accumulator;

}

return factorial($n - 1, $accumulator * $n);

}

var_dump(factorial(100));

?>

可惜测试后才发现PHP根本不支持尾调用!好在天无绝人之路,仔细阅读维基百科中关于尾调用的介绍,你会发现里面提到了Trampoline的概念。简单点说就是利用高阶函数消除递归,依照这样的理论基础,我们可以把上面的尾调用代码改写成如下方式:

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)));

?>

看上去不错,不过我不得不向大家道个歉,本文用递归实现阶乘其实是个玩笑,实际上只要用一个循环就行了,《代码大全》里专门提到了这一点:

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等技术来规避潜在的栈溢出问题。

php recursion,PHP与Recursion 学习笔记相关推荐

  1. 数据结构学习笔记(六):二叉树(Binary Tree)

    目录 1 背景知识:树(Tree) 2 何为二叉树(Binray Tree) 2.1 二叉树的概念与结构 2.2 满二叉树与完全二叉树 2.3 二叉树的三种遍历方式 3 二叉树及其遍历的简单实现(Ja ...

  2. redhat6.x_linux学习笔记

    ULE 学习笔记 ls -l install.log -:rw-r--r--: 1 :root: root :39410 :2012-12-03 :install.log 文件类型 : 权限位 : 硬 ...

  3. Python基础学习笔记之(一)

    Python基础学习笔记之(一) zouxy09@qq.com http://blog.csdn.net/zouxy09 前段时间参加微软的windows Azure云计算的一个小培训,其中Pytho ...

  4. 《C Primer Plus》学习笔记—第9章

    目录 <C Primer Plus>学习笔记 第9章 函数 1.复习函数 1.引入 2.创建并使用简单的函数:程序lethead1.c 3.分析程序 4.函数参数 1.程序lethead2 ...

  5. Linux 学习笔记2 常用命令

    Linux 学习笔记1 安装linux详细教程_O丶ne丨柒夜的博客-CSDN博客 Linux_目录结构 基本介绍 1.liux的文件系统是采用级层式的树状目录结构,在此结构中的最上层是根目录&quo ...

  6. Programming Languages PartA Week2学习笔记——SML基本语法

    Programming Languages PartA Week2学习笔记--SML基本语法 首先简单介绍使用的SML语言,参考维基百科和百度百科: ML(Meta Language:元语言)是由爱丁 ...

  7. 郑志远的java学习笔记

    郑志远的java学习笔记 郑志远    CCB 1         Java起源 略 2         Java语言概述 1.面向对象编程的3个原则:封装(encapsulation).继承(inh ...

  8. Modern C++ 学习笔记——C++函数式编程

    往期精彩: Modern C++ 学习笔记--易用性改进篇 Modern C++ 学习笔记 -- 右值.移动篇 Modern C++ 学习笔记 -- 智能指针篇 Modern C++ 学习笔记 -- ...

  9. 《JavaScript语言精粹》学习笔记(函数(2))

    <JavaScript语言精粹>学习笔记(函数(2)) 函数(Functions) 参数(Arguments) 当参数被调用时,会得到一个"免费"的参数数组argume ...

  10. SICP学习笔记(1.1.6)

                                         SICP学习笔记(1.1.6)                                             周银辉 ...

最新文章

  1. 百度开源联邦学习框架 PaddleFL:简化大规模分布式集群部署
  2. 高效开发 Android App 的 10 个建议(转)
  3. java 数组排序论文_Java中运用数组的四种排序方法
  4. ESP32 OTA升级框架
  5. linux下安装glibc-2.14,解决“`GLIBC_2.14' not found”问题
  6. [SceneKit专题]11-Reference-Nodes引用节点
  7. 如何优化网页的加载速度
  8. 安装nodejs出现Invalid drive: f:\的解决办法
  9. java 接收soap_2.技巧: 用 JAXM 发送和接收 SOAP 消息—Java API 使许多手工生成和发送消息方面必需的步骤自动化...
  10. 罗技鼠标驱动ghub怎么设置?
  11. python的快捷键总结
  12. 使用阿里云服务器搭建自己的个人网站
  13. 我为什么从 Google 辞职,开始为自己工作?
  14. 金仓数据库KingbaseES之自增列
  15. java正整数分解因数_java将一个正整数分解质因数
  16. 查看Window系列本地账户密码
  17. 站长天下:buyren网上购物论坛的发展
  18. C语言获取硬件信息(CPU序列号,硬盘序列号,网卡IP、MAC地址、是否插入网线)
  19. 74HC14中文资料
  20. myfocus官方网站已经挂掉,相关下载已经从googlecode转到csdn

热门文章

  1. wxWidgets:wxFileDialog类用法
  2. wxWidgets:获取主机的IP地址
  3. wxWidgets:进程间通信
  4. BOOST_SCOPE_EXIT_TPL宏相关的测试程序
  5. boost::multiprecision模块complex128相关的测试程序
  6. boost::mpi模块实现scan集合的测试
  7. boost::gil::rgb8_image_t::recreate用法的测试程序
  8. GDCM:gdcm::Overlay的测试程序
  9. ITK:对多个线程上的数据进行操作以利用多核处理器
  10. VTK:可视化算法之StreamlinesWithLineWidget