美文网首页
PHP 与 Recursion

PHP 与 Recursion

作者: zerdon | 来源:发表于2014-09-19 23:14 被阅读77次

在程序设计中,递归(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 等技术来规避潜在的栈溢出问题。

相关文章

  • PHP 与 Recursion

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

  • recursion 与lambda函数

    def factorial(n): if n==1: return 1 return n*fact...

  • Binary Tree and Recursion

    DFS Non-recursion (use stack) Recursion (自己调用自己)-- Traver...

  • Recursion

    How to calculate the complexity? How about the space?

  • Recursion

    Fibonacci Find the maximum value among array elements Bub...

  • Recursion

    发自简书 递归 导致递归的方法返回而没有再一次进行递归调用,此时我们称为基值情况( base case)。每一个递...

  • Recursion

    有限记忆和无限思维之间的矛盾促生了语言的递归性 语言是用有限手段生成无限话语的装置.如果一种语法没有递归机制,它将...

  • Recursion

    Fibonacci A frog can jump one or two steps at a time. How...

  • recursion

    22 Generate Parentheses 39 Combination Sum 40 Combination...

  • 递归

    recursion完成了iteration,但逻辑清晰,有以下问题: recursion 由stack完成,会溢出...

网友评论

      本文标题:PHP 与 Recursion

      本文链接:https://www.haomeiwen.com/subject/nolutttx.html