二叉搜索树析构函数

Posted

技术标签:

【中文标题】二叉搜索树析构函数【英文标题】:Binary Search Tree Destructor 【发布时间】:2011-12-22 13:15:06 【问题描述】:

致力于在 C++ 中实现我自己的 BST,以获得处理此类结构的经验。

我在实现析构函数时遇到了麻烦。我在研究中发现,不能真正拥有递归析构函数(由于一个标志不允许在调用同一个对象后调用析构函数),但我不太确定其他成功清理树中所有节点的方法。

作为补偿,我创建了一个辅助函数 - 但是这会在“删除 n”行上引发未解决的外部错误。有什么建议吗?

代码:

void BinSearchTree::Clear(tNode* n)

    if (n->left != NULL)
        Clear(n->left);
    if (n->right != NULL)
        Clear(n->right);
    delete n;
    n = NULL;
    size--;

【问题讨论】:

它与问题无关,但您不需要将变量n设置为NULL,因为它不再在函数中访问。 @JoachimPileborg 设置空状态只是因为可以独立于析构函数调用 Clear 来重置树。在使用 size 数据成员来跟踪此类事情之前,我最初是在测试是否缺少有效的根指针来确定树是否已初始化。 出于好奇,有谁知道在树的标准实现中大小是否通常是一个跟踪值?我认为它没有什么用处,因为迭代循环在模型上是不现实的。 变量 n 是 this 函数的本地变量。在函数中将其设置为 NULL 不会在函数外部将其设置为 NULL,那么您需要将 n 作为对指针的引用 (tNode *&) 或作为指向指针的指针 (tNode **) 传递。 你的节点中有父指针吗? 【参考方案1】:

只需使用析构函数。听起来你的问题是你试图直接调用析构函数,但是语言会为你处理这个。当您离开堆栈分配对象的范围或删除对象时,它们会被调用。

你只需要这个:

~BinSearchTree() 
    delete left;
    delete right;

delete 将调用它们的析构函数。

注意:delete NULL 绝对安全,has no effect。

【讨论】:

@JeremyFriesner - 你是对的。对不起,一定比我想象的更累。 是的,刚刚也解决了这个问题。需要查看它以确保它是安全的,因为我不经常使用 C++。【参考方案2】:

你可以有一个递归析构函数;你不能做的是两次删除同一个对象。

在 C++ 中删除树的典型方法可能是这样的:

BinSearchTree::~BinSearchTree()

   delete _rootNode;  // will recursively delete all nodes below it as well


tNode::~tNode()

   delete left;
   delete right;

关于未解决的外部错误——当您尝试编译/链接程序时是否会抛出该错误?如果是这样,可能是因为 tNode 类的代码(特别是 tNode 析构函数,如果您声明了一个)不存在或没有编译到您的项目中。

【讨论】:

这似乎是一个愚蠢的澄清需求,但我必须问,因为这不同于我见过的任何递归。在处理链表时,这种递归方法是否仍然比 while 循环更糟糕? 对于链表,我可能只使用 while 循环,因为它与递归执行一样容易正确编写和理解,并且它使用固定数量的堆栈空间。另一方面,通过递归比使用迭代方法更容易处理树——如果您编写一个非递归方法来遍历一棵树,您通常最终还是会实现自己的堆栈。 如果堆栈溢出怎么办? 那么你的程序将会崩溃或者出现其他形式的未定义行为。【参考方案3】:

问题是在你的类中你可能声明了节点结构有一个自定义析构函数,但你没有提供它,所以在链接时编译器会抱怨缺少一块。

如果您在析构函数中不再需要任何自定义代码,那么您只需从结构声明中删除析构函数,您的程序就可以正常编译了。

但是请注意,使用析构函数来破坏子节点完全没有问题(请参阅 Brendan Long 的回答)。如果您在尝试遇到问题时遇到问题,那么您遇到的问题肯定是我的其他问题。

【讨论】:

【参考方案4】:

自动化怎么样:

class BinSearchTree

    std::auto_ptr<tNode>   left;
    std::auto_ptr<tNode>   right;

    BinSearchTree(BinSearchTree const&);
    BinSearchTree& operator=(BinSearchTree const&); // private
;

现在销毁是自动的。 :-)

【讨论】:

仅当您打开了 C++11 时。 std::auto_ptr 非常适合这个。 如果将一棵二叉搜索树分配给另一棵,则孩子将被绑架。这真的是你想要的吗? 绑架比克隆好。暗示 BinSearchTree 已经定义了复制构造函数和赋值运算符(否则 RAW 指针将被克隆,从而导致双重删除)。所以我假设它们是被定义的,但即使它们不是这种技术也可能允许绑架但这总比被克隆好。【参考方案5】:

以前的答案指出,未解决的外部错误很可能是由在链接器可以看到的翻译单元中声明但未定义的 tNode 析构函数引起的。

但是,您还有第二个错误:您似乎认为将 n 设置为 null 会做一些它不会做的事情。指针值 n 是按值传递的,而不是按引用传递的,因此在函数返回后更改其值(例如通过分配 NULL)不会产生任何影响。

当您清除树并期望根节点指针已设置为 NULL 时,这可能会给您带来错误,而它仍然是指向已释放内存的悬空指针。结果将是运行时错误,而不是您的链接器错误。

void BinSearchTree::Clear(tNode **N)

    tNode * n = *N;
    if (n->left != NULL)
        Clear(n->left);
    if (n->right != NULL)
        Clear(n->right);
    delete n;
    *N = NULL;
    size--;

会如你所愿。

【讨论】:

【参考方案6】:

也可以使用递归,只需将函数头改为:

void remove(node*& root) 

它会起作用的。

【讨论】:

以上是关于二叉搜索树析构函数的主要内容,如果未能解决你的问题,请参考以下文章

二叉搜索树介绍和模拟实现

二叉搜索树介绍和模拟实现

搜索二叉树--C++

二叉搜索树介绍与实现

二叉搜索树

二叉搜索树的操作集