【问题标题】:What problems can I solve using recursion? [closed]使用递归可以解决哪些问题? [关闭]
【发布时间】:2018-01-05 16:47:55
【问题描述】:

今天我做了一种在 php 中使用递归的方法,并对它产生了兴趣。递归实际上可以解决哪些问题?对于哪些事情我们可以使用递归?

我开始在网上搜索它并搜索和搜索,但我找不到任何东西。

所以我问,我们可以用递归做什么?

有一些限制吗?我们不能用递归但标准循环可以做的事情?我想知道我是否可以在我的代码中经常使用 now 递归。

【问题讨论】:

  • 欢迎来到 SO!看看这篇文章,了解如何写好问题Lstackoverflow.com/help/how-to-ask
  • 在大多数情况下,但不是所有情况下,您都可以使用常规循环而不是递归。与循环解决方案相比,递归允许更小(更整洁?)的代码。见:stackoverflow.com/questions/1094679/… & stackoverflow.com/questions/660337/recursion-vs-loops
  • 基本上任何部分解决方案导致不同输入的相同问题的问题;例如硬币找零:我有一些硬币,需要支付 5.23 欧元;我使用 2 欧元硬币,然后我收集的硬币较少,我需要支付 3.23 欧元;或者我从 1 欧元硬币开始,我仍然需要支付 4.23 欧元。分区是递归的另一个明显候选者。或生成排列。

标签: php algorithm loops recursion


【解决方案1】:

我们可以用递归做什么?

解决递归问题。最常见的问题是每个孩子都可以再次成为父母的亲子问题。

function doSomethingWithNode($node) 
{
    // Do something with $node

    // Loop over all childs, and run this code for those childs too, and for those childs, and for those childs, and ...
    foreach($node->getChilds() as $child) {
        doSomethingWithNode($child);
    }
}

doSomethingWithNode($rootNode);

有什么限制吗?

是的。 PHP(和其他编程语言)跟踪哪些代码调用了哪个函数,因此它知道函数返回后从哪里继续。这称为call stack。向call stack 添加新条目需要一些内存和一些时间。当您进行大量(数百万)次迭代时,主要是第一个 CAN 会导致问题。

根据您的安装,call stack 甚至可能会受到限制。默认情况下不是。通过安装xdebug-extension,它可以为您提供最多 100 个嵌套调用(默认情况下,可以在配置中更改)。在这些设置中,它会导致致命错误 (example)。

我们不能用递归做的事情,但我们可以用标准循环做?我想知道我是否可以在我的代码中经常使用 now 递归

由于上述限制,应谨慎使用递归。当你可以用普通的while或for循环解决它时:使用普通的循环。大多数情况下,它会导致更容易阅读代码。

当您关心可移植性时(例如在编写开源项目时),您可能希望考虑到 xdebug-users。

【讨论】:

  • PHP 根本不限制调用堆栈。一个简单的递归,打印了每个百万次调用我在 55 之后停止,因为它开始变得有点滞后并用完大约 11GB 内存,但它不会停止,直到它不能分配一个新的帧。您可能安装了 xdebug,它增加了一个可配置的限制。 AFAIK 在您选择递归解决方案的频率方面,这击败了许多其他 Algol 方言。
  • 我同意,在我的脚本中多次达到脚本执行的最大时间,所以不能限制调用堆栈
  • @Sylwester 显然我在启用 xdebug-extension 的情况下开发了太长时间,并认为这是默认的 PHP 行为。我已经更新了我的答案以纠正这个问题。自我注意:周一早上第一件事就是改变 xdebug-settings ;)
  • @JustynaBrosławska 我已经更新了我的答案,请参阅上面的评论(只能在评论中添加一个 @...)
【解决方案2】:

通过任何数据结构的每一种迭代都可以通过递归来完成。实际上,您可以将每个循环构造替换为简单的递归,例如 forwhiledo whileforeach 等:

function factorial($n) {
  $result = $n < 0 ? -1 : 1;
  for($i = abs($n), $i > 1; $i--) {
    $result *= $i; 
  }
  return $result;
}

可以写成:

function factorial($n) {
  $forHelper = function($result, $i) {
    return $i === 1 ? $result : $forHelper($result * $i, $i - 1);
  }
  return $forHelper($n < 0 ? -1 : 1, abs($n)); 
}

这称为尾递归。在某些语言中,上述两者产生非常相似的运行时代码,但不是 PHP。它每次调用都会使用一些内存。对我来说,两者都具有同样的可读性,但我敢打赌大多数程序员会发现循环更容易,尤其是当它被嵌套时。

一些迭代图或树的算法将有一个使用递归而不是迭代的更简单的版本:

function treeDepth($node) {
  if( $node === null ) {
    return 0;
  } else {
    return 1 + max(treeDepth($node->left), treeDepth($node->right));
  }
}

这里的迭代解决方案需要某种方式来跟踪它需要访问的地方,因此您正在做一些由递归版本自动处理的内务处理。

function treeDepth($node) {
  $max = 0;
  $backtrack = [[0, $node]];
  while( count($backtrack) ) {
    list($depth, $node) = array_pop($backtrack); 
    while($node !== null) {
      array_push($backtrack, [++$depth, $node->right]);
      $node = $node->left; 
    }
    $max = max($max, $depth);
  }
  return $max; 
}

限制取决于语言。在 PHP 中,它为每个调用帧分配一些内存,而这些内存不是为简单循环分配的。节点遍历总是使用内存,因为该过程本质上是递归的。

最终,您应该以尽可能清晰的方式编写代码,而无需考虑优化。在最清晰的地方使用循环结构,在最清晰的地方使用递归。只有当您真正偶然发现性能问题时,您才应该分析并重写最耗时的部分。如果原始代码很短,我会将其保留为注释,并记录在更复杂的迭代版本中实际发生的情况。在 PHP 中,函数调用本身很昂贵。

【讨论】:

    猜你喜欢
    • 2010-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 1970-01-01
    • 2011-01-09
    • 2015-08-25
    • 2013-03-25
    相关资源
    最近更新 更多