遍历BST时出现Stackoverflow异常

Stackoverflow exception when traversing BST

本文关键字:Stackoverflow 异常 BST 遍历      更新时间:2023-10-16

我在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);
}

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

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

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

它也有显示代码的示例。

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

平衡你的树偶尔。如果你的树在FindHeight()上得到stackoverflow,这意味着你的树是方式不平衡。如果树是平衡的,那么100000个元素的深度应该只有20个节点。

重新平衡非平衡二叉树的最简单(但相当缓慢)的方法是分配一个足够大的TItem数组来容纳树中的所有数据,按排序顺序插入所有数据,并删除所有节点。然后从数组递归地重新构建树。根节点是中间的节点。root->left是左半部分的中间,root->right是右半部分的中间。重复递归。这是最简单的重新平衡方法,但它很慢,并且暂时占用大量内存。另一方面,只有当您检测到树非常不平衡(插入深度超过100)时才需要执行此操作。

另一个(更好的)选项是在插入期间进行平衡。最直观的方法是跟踪当前节点下面有多少个节点。如果右子节点的"子"节点数是左子节点的两倍以上,则向左"旋转"。反之亦然。网上到处都是如何旋转树的说明。这使得插入稍微慢一些,但这样就不会出现第一个选项造成的偶尔的大量停顿。另一方面,您必须在旋转时不断更新所有"子"计数,这不是微不足道的。

为了在没有递归的情况下计算叶子,可以使用迭代器的概念,就像STL对std::setstd::map底层的rb树所使用的那样…为您的树创建一个begin()end()函数,用于标识有序的第一个和最后一个节点(在本例中是最左边的节点,然后是最右边的节点)。然后创建一个名为

的函数

BinarySearchTreeNode* increment(const BinarySearchTreeNode* current_node)

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

你的increment()算法看起来像这样:

  1. 检查当前节点是否有右子节点。
  2. 如果有右子树,使用while循环查找右子树的最左边的节点。这将是"下一个"节点。
  3. 如果当前节点没有右子节点,则检查当前节点是否是其父节点的左子节点。
  4. 如果步骤#3为真,则"下一个"节点为父节点,因此您可以在此停止,否则执行下一步。
  5. 如果步骤#3为假,则当前节点是父节点的右子节点。因此,您需要使用while循环继续向上移动到下一个父节点,直到遇到其父节点的左子节点为止。这个左子节点的父节点将是"下一个"节点,你可以停止。
  6. 最后,如果步骤#5返回到根节点,那么当前节点就是树中的最后一个节点,迭代器已经到达树的末端。

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

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

BTW,不幸的是,这种方法的复杂性将是O(N)…远不如O(log N)好