如何将递归函数转换为使用堆栈?

Posted

技术标签:

【中文标题】如何将递归函数转换为使用堆栈?【英文标题】:How to convert a recursive function to use a stack? 【发布时间】:2011-03-24 09:46:01 【问题描述】:

假设我有一棵树要使用深度优先搜索来遍历,并且我的遍历它的算法看起来像这样:

algorithm search(NODE):
  doSomethingWith(NODE)
  for each node CHILD connected to NODE:
    search(CHILD)

现在在许多语言中,递归都有一个最大深度,例如,如果递归深度超过某个限制,那么过程将因堆栈溢出而崩溃。

如何在没有递归的情况下使用堆栈来实现此功能?在很多情况下,有很多局部变量;它们可以存放在哪里?

【问题讨论】:

我发现这个问题有一点无意的幽默,因为大多数编程语言(尽管不是全部)都会在内部为此使用堆栈。当然,还有一个事实是,对于大多数语言来说,使用深度优先搜索达到递归限制将需要你有一个非常不平衡的树或一个非常非常大的树,考虑到你需要一个深度大约 1000 个,并且大多数平衡二叉树的元素数量等于 2^depth - 1,这意味着对于该树,在溢出之前您需要 2^1000 - 1 个元素。 当然,由于一种语言可能会使用堆栈来实现幕后的递归,因此导致递归形式溢出的任何原因都可能成为显式使用的问题-堆栈版本也是如此。 (不过,我不觉得这是一个坏问题;我现在只是感觉有点不自在,心情很乱。) 事实上我的树非常非常大,尽管有大量相同的节点。因此,相同的节点被缓存在哈希表中,但树仍然非常大。 【参考方案1】:

您将其更改为使用这样的堆栈:

algorithm search(NODE):
  createStack()
  addNodeToStack(NODE)

  while(stackHasElements)
      NODE = popNodeFromStack()
      doSomethingWith(NODE)
      for each node CHILD connected to NODE:
         addNodeToStack(CHILD)

关于你的第二个问题:

在很多情况下,有很多局部变量;它们可以存放在哪里?

这些确实可以保存在与原来相同的位置。如果变量是“doSomethingWith”方法的本地变量,只需将它们移入其中,并将其重构为单独的方法。该方法不需要处理遍历,只需要处理,并且可以通过这种方式拥有自己的局部变量,仅在其范围内工作。

【讨论】:

也许很明显,但是如果节点有本地状态,除了本地处理变量,那么你只需将它们添加到NODE的定义中。【参考方案2】:

Eric Lippert 已就该主题创建了许多帖子。例如看看这个: Recursion, Part Two: Unrolling a Recursive Function With an Explicit Stack

【讨论】:

链接已损坏【参考方案3】:

对于稍微不同的遍历。

push(root)
while not empty:
    node = pop
    doSomethingWith node
    for each node CHILD connected to NODE:
        push(CHILD)

对于相同的遍历,以相反的顺序推送节点。

如果你要炸掉你的堆栈,这可能不会有帮助,因为你会炸掉你的堆

如果你有一个 nextChild 函数,你可以避免推所有的孩子

【讨论】:

你通常会在堆之前破坏你的堆栈...堆栈内存大小通常被限制在一个相当低的量(即:1mb)。 是的,但是如果您的递归足以炸掉 1M 堆栈,那么您几乎总是做错了足以轻松炸掉另外 3 个数量级的事情。【参考方案4】:

基本上你新建了你自己的堆栈:char a[] = new char[1024]; 或为了类型安全,node* in_process[] = new node*[1024]; 并将你的中间值放在上面:

node** current = &in_process[0];
node* root = getRoot();

recurse( root, &current) ;**

void recurse( node* root, node** current ) ;
  *(*current)++ = root; add a node
  for( child in root ) 
    recurse( child, current );
  
  --*current; // decrement pointer, popping stack;

【讨论】:

以上是关于如何将递归函数转换为使用堆栈?的主要内容,如果未能解决你的问题,请参考以下文章

用栈将递归转换成非递归

您如何将其转换为迭代函数而不是使用嵌套循环进行递归?

为什么编译器无法自动优化常规递归?

java - 如何使用堆栈安全(基于堆)递归将二叉树转换为java中的列表?

如何将flattenObj函数从ramda cookbook转换为迭代函数

递归函数中的堆栈实现