【问题标题】:Stackoverflow exception when traversing BST遍历 BST 时的 Stackoverflow 异常
【发布时间】:2011-12-25 18:42:34
【问题描述】:

我在 C++ 中为我的一项任务实现了一个基于链接的 BST(二叉搜索树)。我已经编写了整个班级并且一切正常,但我的作业要求我绘制以下运行时间:

a.  A sorted list of 50000, 75000, and 100000 items
b.  A random list of 50000, 75000, and 100000 items

没关系,我可以插入数字,但它也要求我调用树上的 FindHeight()CountLeaves() 方法。我的问题是我已经使用recursion 实现了这两个功能。由于我有一个如此大的数字列表,我得到了一个stackoverflow 异常。

这是我的类定义:

template <class TItem>
class BinarySearchTree
{
public:
    struct BinarySearchTreeNode
    {
    public:
        TItem Data;
        BinarySearchTreeNode* LeftChild;
        BinarySearchTreeNode* RightChild;
    };

    BinarySearchTreeNode* RootNode;

    BinarySearchTree();
    ~BinarySearchTree();

    void InsertItem(TItem);

    void PrintTree();
    void PrintTree(BinarySearchTreeNode*);

    void DeleteTree();
    void DeleteTree(BinarySearchTreeNode*&);

    int CountLeaves();
    int CountLeaves(BinarySearchTreeNode*);

    int FindHeight();
    int FindHeight(BinarySearchTreeNode*);

    int SingleParents();
    int SingleParents(BinarySearchTreeNode*);

    TItem FindMin();
    TItem FindMin(BinarySearchTreeNode*);

    TItem FindMax();
    TItem FindMax(BinarySearchTreeNode*);
};

FindHeight() 实现

template <class TItem>
int BinarySearchTree<TItem>::FindHeight()
{
    return FindHeight(RootNode);
}

template <class TItem>
int BinarySearchTree<TItem>::FindHeight(BinarySearchTreeNode* Node)
{
    if(Node == NULL)
        return 0;

    return 1 + max(FindHeight(Node->LeftChild), FindHeight(Node->RightChild));
}

CountLeaves() 实现

template <class TItem>
int BinarySearchTree<TItem>::CountLeaves()
{
    return CountLeaves(RootNode);
}

template <class TItem>
int BinarySearchTree<TItem>::CountLeaves(BinarySearchTreeNode* Node)
{
    if(Node == NULL)
        return 0;
    else if(Node->LeftChild == NULL && Node->RightChild == NULL)
        return 1;
    else
        return CountLeaves(Node->LeftChild) + CountLeaves(Node->RightChild);
}

我试图考虑如何在不递归的情况下实现这两种方法,但我完全被难住了。有人有什么想法吗?

【问题讨论】:

  • (a) 问题标题应该描述问题,而不是你的情绪状态/希望&梦想; (b) 我们不在乎您的任务何时到期!; (c) 无需在帖子上签名。祝你好运!
  • @SaadImran:可以肯定地说插入没有任何平衡吗?
  • @SaadImran:一时兴起,如果您在发布版本中使用BinarySearchTreeNode* 的函数原型之前加上static 这个词,它仍然会溢出吗?例如:static void PrintTree(BinarySearchTreeNode*);
  • @MooingDuck 是的,你是对的,我并没有真正考虑足够远来平衡树,我只是将数字 1-100,000 插入到树中。我将尝试平衡以及添加 static 关键字。谢谢!
  • 研究使用红黑语义进行平衡。使用适当平衡的树,您只需执行ceil(lg n) 级别的递归。即使函数是非静态的,也不应该用完堆栈空间。

标签: c++ recursion stack-overflow binary-search-tree


【解决方案1】:

为了在没有递归的情况下计算叶子,请使用迭代器的概念,就像 STL 用于基础 std::setstd::map 的 RB-tree ... 为您创建一个 begin()end() 函数树标识有序的第一个和最后一个节点(在这种情况下是最左边的节点,然后是最右边的节点)。然后创建一个名为

的函数

BinarySearchTreeNode* increment(const BinarySearchTreeNode* current_node)

对于给定的current_node,将返回指向树中下一个节点的指针。请记住,要使此实现正常工作,您需要在您的 node 类型中添加一个额外的 parent 指针来帮助迭代过程。

increment() 的算法如下所示:

  1. 检查当前节点是否有右子节点。
  2. 如果存在右子节点,则使用 while 循环查找该右子树的最左侧节点。这将是“下一个”节点。否则转至第 3 步。
  3. 如果当前节点上没有右子节点,则检查当前节点是否是其父节点的左子节点。
  4. 如果步骤#3为真,则“next”节点为父节点,此时可以停止,否则进行下一步。
  5. 如果步骤#3 为假,则当前节点是父节点的右子节点。因此,您将需要使用 while 循环继续向上移动到下一个父节点,直到遇到作为其父节点的左子节点的节点。这个左子节点的父节点将成为“下一个”节点,您可以停止。
  6. 最后,如果第 5 步返回根节点,则当前节点是树中的最后一个节点,并且迭代器已到达树的末尾。

最后,您需要一个bool leaf(const BinarySearchTreeNode* current_node) 函数来测试给定节点是否为叶节点。因此,您的计数器函数可以简单地遍历树并找到所有叶节点,完成后返回最终计数。

如果您想在不递归的情况下测量不平衡树的最大深度,您将需要在树的insert() 函数中跟踪节点插入的深度。这可以只是您的 node 类型中的一个变量,该变量是在将节点插入树中时设置的。然后您可以遍历这三个,并找到叶节点的最大深度。

顺便说一句,不幸的是,这种方法的复杂性将是 O(N) ...远没有 O(log N) 好。

【讨论】:

  • 我看不出这如何有助于找到深度,除非它存储在节点中。
  • 另外,这需要一些返工,因为迭代需要指向父级的反向指针。
【解决方案2】:

偶尔平衡你的树。如果您的树在 FindHeight() 上出现 stackoverflow,这意味着您的树 方式 不平衡。如果树是平衡的,对于 100000 个元素,它应该只有大约 20 个节点的深度。

重新平衡不平衡二叉树的最简单(但相当慢)的方法是分配一个 TItem 数组,该数组大到足以容纳树中的所有数据,将所有数据按排序顺序插入其中,并删除所有个节点。然后递归地从数组重建树。根是中间的节点。 root-&gt;left 是左半边的中间,root-&gt;right 是右半边的中间。递归地重复。这是重新平衡的最简单方法,但速度较慢并且暂时占用大量内存。另一方面,只有在检测到树非常不平衡时才需要这样做(插入深度超过 100)。

另一个(更好的)选择是在插入过程中保持平衡。最直观的方法是跟踪当前节点下方有多少节点。如果右孩子的“孩子”节点数量是左孩子的两倍多,则向左“旋转”。反之亦然。有关于如何在互联网上旋转树的说明。这会使插入速度稍微慢一些,但是您不会遇到第一个选项造成的偶尔的大规模停顿。另一方面,您必须在旋转时不断更新所有“子”计数,这并非易事。

【讨论】:

    【解决方案3】:

    我发现this page 非常有启发性,因为它讨论了将使用递归的函数转换为使用迭代的函数的机制。

    它也有显示代码的示例。

    【讨论】:

    • 只显示尾递归,而他的两个都是二进制递归,如果不在其他地方建立堆栈,很难转换为迭代。
    • 很难?我觉得你夸大了。
    • 这比构建堆栈要困难得多。然而,在这一点上,建立一个堆栈可能是他最简单(如果不是最正确)的路线。
    • 有一个函数在迭代中创建显式堆栈比在递归中使用隐式堆栈要复杂得多:Ackermann 函数。
    • @moshbear:我承认可能存在这样的例子,我只是从未遇到或听说过。不过,阿克曼函数是有意义的。
    【解决方案4】:

    您可能需要在插入时计算此值。存储节点的高度,即在 Node 对象中添加一个整数字段,如高度。也有柜台高度和树的叶子。当您插入一个节点时,如果其父节点是(曾经)叶子,则叶子计数不会改变,但如果不是,则将叶子计数增加 1。新节点的高度也是父节点的高度 + 1,因此如果它更大比树的当前高度,然后更新它。它是一个家庭作业,所以我不会帮助实际的代码

    【讨论】:

    • 谢谢,我确实考虑过这一点,但我认为既然他想要一个函数,他实际上希望我们遍历树并在最后计算它。如果我无法及时弄清楚如何正确平衡它,我可能最终会这样做。谢谢!
    【解决方案5】:

    如果它是平衡的,那么具有 100,000 个节点的树上的递归应该不是问题。深度可能只有 17,在所示的实现中不会使用太多堆栈。 (log2(100,000) = 16.61)。所以看起来可能是构建树的代码没有正确平衡它。

    【讨论】:

    • 或更可能,插入根本不平衡。
    • @Mooing:我同意这很有可能。这意味着对于一个排序列表,BST 基本上会产生一个包含 100,000 个项目的长链表。
    • 嗯,我没想到这么远。我从 0 循环到 100,00 并插入循环控制变量,因此我列表中的所有节点都向右分支。我会尝试平衡树。感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多