如何避免递归函数的堆栈溢出?

时间:2013-07-01 04:21:00

标签: c++ c recursion stack-overflow

例如,如果我们通过跟随函数遍历一个相当大的树,则可能会导致堆栈溢出。

void inorder(node* n)
{
  if(n == null) return;
  inorder(n->l);
  n->print();
  inorder(n->r); 
}

如何在函数中添加条件或某些东西以防止发生此类溢出?

7 个答案:

答案 0 :(得分:6)

考虑迭代迭代,如果这确实是一个问题。

http://en.wikipedia.org/wiki/Tree_traversal

在那里查看psedo代码进行迭代 iterativeInorder iterativePreorder iterativePostorder

在while循环中基本上使用自己的列表作为堆栈数据结构,可以有效地替换函数递归。

答案 1 :(得分:1)

关于递归的事情是,你永远不能保证它永远不会溢出堆栈,除非你可以在(最小)内存大小上设置一些界限和(最大)输入的大小。但是, 可以做的是保证如果你有一个无限循环,溢出堆栈......

我看到你的“if()返回”;终止条件,所以你应该避免无限循环,只要你的树的每个分支都以null结尾。因此,一种可能性是格式错误的输入,其中树的某些分支永远不会达到空。 (例如,如果树数据结构中有循环,则会发生这种情况。)

我看到的另一个可能性是你的树数据结构对于可用的堆栈内存量来说太大了。 (N.B.这是虚拟内存和交换空间可以使用,因此它不一定是RAM不足的问题。)如果是这种情况,您可能需要提出一些不递归的其他算法方法。虽然你的函数占用的内存很小,但除非你省略了一些额外的处理,否则你的树真的必须非常深刻才能成为一个问题。 (N.B.这是一个问题的最大深度,而不是节点的总数。)

答案 2 :(得分:1)

您可以增加操作系统的堆栈大小。如果您处于类Unix环境中,通常会通过ulimit进行配置。

E.g。在Linux上,您可以ulimit -s unlimited将堆栈的大小设置为“无限制”,尽管IIRC有一个硬限制,您不能将整个内存专用于一个进程(尽管下面链接中的一个答案提到无限量)。

我的建议是运行ulimit -s,这会给你当前的堆栈大小,如果你仍然得到一个堆栈溢出超过这个限制,直到你满意为止。

查看hereherehere,详细了解堆栈的大小以及如何更新它。

答案 3 :(得分:1)

如果你有一个非常大的树,并且你遇到了使用递归遍历来覆盖堆栈的问题,那么问题很可能是你没有一个平衡的树。第一个建议是尝试平衡的二叉树,例如红黑树或AVL树,或每个节点有超过2个子节点的树,例如B +树。 C ++库提供std::map<>std::set<>,它们通常使用平衡树实现。

但是,避免递归顺序遍历的一种简单方法是修改树的线程。也就是说,使用叶子节点的右指针指示下一个节点。这种树的遍历看起来像这样:

n = find_first(n);
while (! is_null(n)) {
    n->print();
    if (n->is_leaf()) n = n->r;
    else n = find_first(n->r);
}

答案 4 :(得分:1)

除了替换递归之外,没有可移植的解决方案 显式管理堆栈(使用 std::vector<Node*>)。非便携式,你可以跟踪 深度使用静态变量;如果你知道最大堆栈 大小,以及每次递归所需的堆栈数,然后就可以了 检查深度是否超过该深度。

许多系统,如Linux和Solaris,你都无法知道 由于堆栈已分配,因此前面的最大堆栈深度 动态。至少在Linux和Solaris下,一次 内存已分配给堆栈,它将保持分配状态 并继续受到影响。所以你可以公平地递归 深深地在程序的开始(可能崩溃,但是 在做任何事情之前),然后检查这个值 后:

static char const* lowerBound = nullptr;

//  At start-up...
void
preallocateStack( int currentCount ) {
{
    char dummyToTakeSpace[1000];
    -- currentCount;
    if ( currentCount <= 0 ) {
        lowerBound = dummyToTakeSpace;
    } else {
        preallocateStack( currentCount - 1 );
    }
}

void
checkStack()
{
    char dummyForAddress;
    if ( &dummyForAddress < lowerBound ) {
        throw std::bad_alloc();   //  Or something more specific.
    }
}

你会注意到有几个案例 在该代码中浮动的未定义/未指定的行为,但是 我已经成功地使用了它几次(在 Sparc上的Solaris,但PC上的Linux在这方面完全相同 看待)。事实上,它几乎适用于任何系统: - 堆栈增长,并且 - 在堆栈上分配局部变量。 因此,它也适用于Windows,但如果它失败了 分配初始堆栈,你必须重新链接,而不是 只是在活动较少的时刻运行程序 框(或更改ulimits)(因为Windows上的堆栈大小 在链接时确定。

编辑:

关于使用显式堆栈的一条评论:一些 系统(包括Linux,默认情况下)overcommit,这意味着 当你无法可靠地获得内存不足错误 延伸std::vector<>;系统会告诉你 std::vector<>记忆在那里,然后给出 在尝试访问时阻止段违规。

答案 5 :(得分:0)

您可以添加静态变量以跟踪调用函数的时间。如果它接近您认为会使系统崩溃的情况,请执行一些例程以通知用户错误。

答案 6 :(得分:0)

可以通过将另一个int变量与递归函数相关联来进行更改的小原型。您可以将变量作为参数传递给函数,默认情况下在根处以零值开始,并在您下降时将其递减树......

缺点:这个解决方案的代价是为每个节点分配一个int变量的开销。

void inorder(node* n,int counter)
{
 if(counter<limit) //limit specified as per system limit
 {
  if(n == null) return;
  inorder(n->l,counter-1);
  n->print();
  inorder(n->r,counter-1);
 }
 else
  n->print();
}

考虑进一步研究: 虽然如果只考虑递归,问题可能不是遍历。并且可以通过更好的树创建和更新来避免。如果没有考虑过,请检查平衡树的概念。