了解递归

In one of my previous articles I wrote about iterators and how you can use them. Today I’d like to look at the fraternal twin of iteration: recursion.

在我以前的一篇文章中, 我写了关于迭代器以及如何使用它们的文章。 今天,我想看看兄弟般的迭代孪生:递归。

Before we talk about recursion, though, let’s take a look at this snippet of code:

不过,在讨论递归之前,让我们看一下这段代码:

<?php
function factorial($number) {
    if ($number < 0) {
        throw new InvalidArgumentException('Number cannot be less than zero');
    }
    $factorial = 1;	
    while ($number > 0) {
        $factorial *= $number;
        $number --;
    }
    return $factorial;
}

A factorial is the result of a number multiplied by all positive integers less than that number, and the function above calculates the factorial of any number given to it using a simple loop. Now let’s rewrite the example this way:

阶乘是一个数字乘以小于该数字的所有正整数的结果,并且上面的函数使用一个简单的循环来计算赋予它的任何数字的阶乘。 现在,让我们以这种方式重写示例:

<?php
function factorial_recursive($number) {
    if ($number < 0) {
        throw new InvalidArgumentException('Number cannot be less than zero');
    }
    if ($number == 0) {
        return 1;
    }
    return $number * factorial_recursive($number – 1);
}

We get the same results when we call both functions, but notice that the second function calculates the factorial by calling itself. This is known as recursion.

当我们调用两个函数时,我们得到相同的结果,但是请注意,第二个函数通过调用自身来计算阶乘。 这称为递归。

什么是递归? (What’s Recursion?)

A recursive function is one that calls itself, either directly or in a cycle of function calls.

递归函数是直接或在函数调用循环中调用自身的函数。

Recursion can also refer to a method of problem solving that first solves a smaller version of the problem and then uses that result plus some other computation to formulate an answer to the original problem. Often times, in the process of solving the smaller version, the method will solve yet a smaller version of the problem, and so on, until it reaches a “base case” which is trivial to solve.

递归还可以指的是一种解决问题的方法,该方法首先解决问题的较小版本,然后使用该结果加上一些其他计算来为原始问题制定答案。 通常,在解决较小版本的过程中,该方法将解决问题的较小版本,依此类推,直到达到“基本情况”为止,这是微不足道的。

To write a recursive function, you need to provide it with some means of return or else it will keep calling itself for eternity (or until the call stack blows up, the script times out, or memory is exhausted). This is known as a guard clause or base case.

要编写递归函数,您需要为它提供一些返回方法,否则它将一直调用自己直到永远(或者直到调用堆栈崩溃,脚本超时或内存耗尽为止)。 这称为保护子句或基本案例。

The simplest form of a recursive function is as follows:

递归函数的最简单形式如下:

<?php
function my_recursive_func (args) {
    if (simplest case) {
        // The Base Case/Guard Clause that stops the
        // function from running forever
        return simple value;
    }
    else {
        //call function again with simpler args
        my_recursive_func(argsSimplified);
    }
}

递归类型 (Types of Recursion)

When a function calls itself directly, it is referred to as direct recursion. A function in a cycle of function calls that eventually invokes itself is called indirect recursion. Look at the example below of indirect recursion:

当函数直接调用自身时,称为直接递归。 最终调用自身的功能调用循环中的功能称为间接递归。 请看下面的间接递归示例:

<?php
function A($num) {
    $num -= 1;
    if($num > 0) {	
        echo "A is Calling B($num)n";
        $num = B($num);
    }
    return $num;
}

function B($num) {
    $num -= 2;
    if($num > 0) {
        echo "B is Calling A($num)n";
        $num = A($num);
    }
    return $num;
}

$num = 4;
echo "Calling A($num)n";
echo 'Result: ' . A($num);
Calling A(4) 
A is Calling B(3) 
B is Calling A(1) 
Result: 0

The above example is really useless code just meant to show you how a function can call itself indirectly through another function. Calling either A(n>4) or B(n>4) causes the called function to be called from the other function.

上面的示例实际上是无用的代码,仅用于向您展示一个函数如何通过另一个函数间接调用自身。 调用A(n>4)B(n>4)会导致从另一个函数中调用被调用函数。

It’s important to know a function can call itself indirectly like this, but in this article we’ll only deal with direct recursion.

重要的是要知道函数可以像这样间接地调用自身,但是在本文中,我们将仅处理直接递归。

一个实际的例子 (A Practical Example)

To show you how powerful recursion can be, we’ll write a function that searches for a key within an array and returns the result.

为了向您展示强大的递归功能,我们将编写一个函数来搜索数组中的键并返回结果。

<?php
function find_in_arr($key, $arr) {
    foreach ($arr as $k => $v) {
        if ($k == $key) {
            return $v;
        }		
        if (is_array($v)) {
            foreach ($v as $_k => $_v) {
	        if ($_k == $key) {
                    return $_v;
                }
            }
        }
    }
    return false;
}

$arr = [
    'name' => 'Php Master',
    'subject' => 'Php',
    'type' => 'Articles',
    'items' => [
        'one' => 'Iteration',
        'two' => 'Recursion',
        'methods' => [
            'factorial' => 'Recursion',
            'fibonacci' => 'Recursion',
        ],
    ],
    'parent' => 'Sitepoint',
];

var_dump(
    find_in_arr('two', $arr),
    find_in_arr('parent', $arr),
    find_in_arr('fibonacci', $arr)
);
string 'Recursion' (length=9)
string 'Sitepoint' (length=9)
boolean false

Things are all well and good, but notice that we iterate only two levels deep into the array, and so the search for “Fibonacci” in the third level fails. If we want to search an array of indeterminate depth, this would not suffice. We can instead rewrite the search as a recursive function:

一切都很好,但是请注意,我们仅迭代数组中的两个级别,因此在第三级别中搜索“ Fibonacci”失败。 如果我们要搜索不确定深度的数组,这是不够的。 相反,我们可以将搜索重写为递归函数:

<?php
function find_in_arr($key, $arr) {
    foreach ($arr as $k => $v) {
        if ($k == $key) {
            return $v;
        }
        if (is_array($v)) {
            $result = find_in_arr($key, $v);
            if ($result != false) {
                return $result;
            }
        }
    }	
    return false;
}

With the recursive function, we can search an array several levels deep since we have not hardcoded how deep the function goes. It just keeps running until it goes over all of the values in the array.

使用递归函数,由于我们没有对函数的深度进行硬编码,因此我们可以搜索几层深度的数组。 它一直保持运行,直到遍历数组中的所有值为止。

头尾递归 (Head and Tail Recursion)

In all of the examples so far we’ve been using what is called head recursion. When the function calls itself, it waits for the result from the call before returning a value of its own. It is possible to write functions in such a way that they do not operate on returned values, but instead pass all required values as parameters. This is known as a tail call (or tail recursion). This method is usually preferred as a language’s runtime can sometimes optimize the calls so that there’s no danger of blowing up the call stack, but PHP does not do this.

到目前为止,在所有示例中,我们一直在使用所谓的head递归。 当函数调用自身时,它将等待调用结果,然后返回自己的值。 可以以这样一种方式编写函数:它们不对返回的值进行操作,而是将所有必需的值作为参数传递。 这称为尾调用(或尾递归)。 通常首选此方法,因为语言的运行时有时可以优化调用,因此没有炸毁调用栈的危险,但是PHP不会这样做。

Below is our factorial example modified to make a tail call. Note that the result of the recursive call is returned, instead of manipulated further.

下面是我们的阶乘示例,经过修改后可以进行尾部调用。 请注意,将返回递归调用的结果,而不是对其进行进一步处理。

<?php
function factorial ($number, $factorial = 1) {
    if ($number < 0) {
        throw new InvalidArgumentException('Number cannot be less than zero (0)');
    }
    if ($number == 0) {
        return $factorial;
    }
    else {
        return factorial($number - 1, $factorial * $number);
    }
}

一般建议 (General Advice)

Any code that can be written iteratively can also be written recursively. However, this is not always easy to do (or even wise to do). Recursion shines when traversing trees and lists or performing most O(n log n) sorts. When you need to divide a repetitive problem up, recursion will fit better than an iterative approach, as in the case of searching within the file system and you need to enter any subdirectories to search within as well. Where there’s the traversal of an indeterminate depth, recursion works great.

可以迭代编写的任何代码也可以递归编写。 但是,这并不总是那么容易做到(甚至明智)。 遍历树和列表或执行大多数O(n log n)排序时,递归会发光。 当您需要划分一个重复的问题时,递归比迭代方法更适合,例如在文件系统内搜索的情况下,您还需要输入任何子目录在其中进行搜索。 遍历不确定深度的地方,递归效果很好。

Keep in mind that PHP does not optimize recursive functions, even if you write them to make tail calls, and recursive functions in general are less efficient and slower than their iterative counterparts, though they sometimes do the job better as shown in the code samples above. Recursion is usually a favored alternative to iteration in functional programming and therefore most functional languages optimize recursive functions.

请记住,即使您编写递尾函数来编写尾部调用,PHP也不会优化递归函数,并且递归函数通常比其迭代对应函数效率更低,速度更慢,尽管它们有时表现更好,如上面的代码示例所示。 。 递归通常是函数编程中迭代的首选替代方法,因此大多数函数语言都会优化递归函数。

If you’re using XDebug, be sure to inspect your system’s configuration. By default you’ll have a limit of 100 recursive calls and if you exceed this, your script will throw a “maximum nested limit reached” error. You can update the debug.max_nesting_level config value if you need to change this.

如果您使用的是XDebug,请确保检查系统的配置。 默认情况下,您将有100个递归调用的限制,如果超出此限制,脚本将抛出“达到最大嵌套限制”错误。 如果需要更改此值,可以更新debug.max_nesting_level配置值。

Finally, it’s a good idea to read an explanation of stack heap and recursion causing stack overflow to understand what happens to the call stack during recursion.

最后,最好阅读有关堆栈堆和递归说明,这些解释会导致堆栈溢出,以了解递归期间调用堆栈发生了什么。

结论 (Conclusion)

In this article I’ve given you a broad look at recursion and how it compares to iteration. I’ve also show you how to write recursive functions, when to write them, and why. I have also tried warn you of some of the pitfalls that you might fall into when using recursion.

在本文中,我对递归及其与迭代的比较进行了广泛的介绍。 我还向您展示了如何编写递归函数,何时编写它们以及为什么。 我还尝试警告您使用递归时可能会遇到的一些陷阱。

Recursion is such that even many experienced programmers can go years without using it and many others have never even heard of it, which is sad because it is a truly powerful concept. I hope with this article I might have given you sufficient knowledge to go out there and start writing your own recursive functions. But remember that like with fire, you have to always be careful and use the tool wisely.

递归使得即使许多有经验的程序员也可以不使用它而花费很多年,而其他许多人甚至从未听说过它,这很可悲,因为它是一个真正强大的概念。 我希望通过本文,我可能已经给您足够的知识,可以在那里开始使用自己的递归函数。 但是请记住,就像着火一样,您必须始终小心并明智地使用该工具。

Image by Alexandre Duret-Lutz via Flickr

图片由Alexandre Duret-Lutz通过Flickr提供

翻译自: https://www.sitepoint.com/understanding-recursion/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值