【问题标题】:C++ - removal of a node in an AVL TreeC++ - 删除 AVL 树中的节点
【发布时间】:2010-12-01 02:24:42
【问题描述】:

我目前正在尝试在 C++ 中实现 AVL 树,到目前为止,除了删除节点之外,我已经完成了几乎所有工作。

1) 有人可以确认我删除节点的算法是正确的吗?

  • 在树中找到要删除的节点
  • 如果节点有0个子节点:删除节点
  • 否则,如果节点有 1 个子节点:删除节点,然后重新链接他的子节点
  • else (2 children):找到它的后继节点,并将要删除的节点与其后继节点交换。然后重复这些步骤以删除节点(现在是其后继节点所在的位置)。
  • 插入后重新平衡树(它会递归地重新平衡树...)

我已经这样做了,但我不确定的部分是我删除节点的步骤。我是否还必须删除对节点的引用,否则它将被管理? (因为我在参数中传入了一个Node*&,但是后继者只是函数中的一个Node*...)

我不确定我是否非常清楚,如果您需要更多详细信息,请告诉我。

(我会发布一些代码,但不幸的是我会说法语,所以我猜你不会从中理解太多)

【问题讨论】:

    标签: c++ pointers reference


    【解决方案1】:

    如果节点是使用“new”创建的,那么绝对需要明确删除它。他们的问题将是在哪里。如果您要删除节点(假设没有其他人需要节点的内容),那么您还应该在完成从树中删除节点后删除节点本身。

    现在,对节点的引用是另一回事。如果引用是方法参数定义的工件,您不必对它做任何事情。该引用是在堆栈上创建的,以帮助方法调用。如果我记得我的 C++,引用永远不能为空,这意味着永远不必说你很抱歉(坏笑话),永远不必删除它们。

    [编辑] OP 说,“因为我已经传入了一个 Node*& 参数,但后继者只是函数中的一个 Node* ......)”所以我假设你的意思是这样的:

    void removeNode(Node*& node) 
    

    然后使用

    调用
    void foo()
    {
        Node* n = new Node();
        // for example
        removeNode(n);
    }
    

    我的 C++ 有点生疏,但这里的想法是 'n' 是一个指针,但 removeNode() 的参数类型是对指针的引用。调用者不知道涉及到引用,它只是传递 'n' 期望 arg 类型是指向节点的指针 (Node*)。编译器将引用创建为参数的包装器,因此只有被调用者知道引用。由于引用是在堆栈上创建的,因此当 removeNode() 返回时,它将被正确“管理”。 'n' 指向的节点仍然需要删除,问题是应该由哪个代码来处理它。

    首先想到的是'removeNode()'来做。一个问题是它只有一个对它的引用,如果您删除指针(引用的目标),则引用将为空,这是一个坏主意/不允许。一想到尝试它的语法就让我畏缩了。

    所以,让客户端代码来做,像这样:

    void foo()
    {
        Node* n = new Node();
        // for example
        removeNode(n);
        delete n;
    }
    

    基本上,您需要为节点指针的范围制定计划。只要它是一致的,你可以用几种不同的方式来做到这一点。如果您希望 removeNode() 处理删除,则将参数类型更改为指针而不是引用并记录调用,因此期望它既可以从树中删除节点,也可以删除内存。

    【讨论】:

    • 您能进一步解释一下吗? : 如果引用是方法参数的工件
    • 感谢您的解释。 :-)
    【解决方案2】:

    您问题的棘手部分是“删除对节点的引用”实际上相当模糊:取决于您认为“删除引用”的内容,答案可能是“是”或“否”。

    在我看来,您将“删除对节点的引用”作为重新链接树的一部分。关键部分是您的方法接收节点指针为Node *&:这样,您的方法不只是获取指向正在审查的节点的指针,它接收到该指针存储位置的引用in父节点。您必须修改此引用以保持树链接完整。

    所以如果你的方法是这样的:

    void deleteNode (Node*& node) {
        if (node is the right one to delete) {
            Node * tmp = node;
            node = node->successor;
            delete node;
        }
        else
            deleteNode(node->successor);
    }
    

    赋值node = node->successor实际上修改了之前节点的后继字段。如果没有此分配,您的树结构将被损坏,因为先前节点的后继字段将指向一个现已释放的节点。

    【讨论】:

    • 这正是我正在做的。
    【解决方案3】:

    如果您使用标准 C++ 进行编码,那么您将无法“管理”任何内容。如果您使用智能指针,那么它们会为您处理解除分配。

    至于你的接口,我建议remove() 函数需要一个指向要删除的节点的指针。然后你不需要费心寻找节点,因为它是给定的。请编写一个find() 函数,以便客户端可以自己找到节点。

    最后,我不确定我是否遵循您的第三种情况,即要删除的节点有两个孩子。在 AVL 树中,当一个节点作为两个子节点删除时,您可以将它与要删除的节点的左子树的最右边的子节点交换。交换后,要删除的节点恰好有一个或零个子节点,因此您基本上将问题简化为这两种情况。

    例如:

                20
         40            15
      60    35     18       10
    80    38  30 19  17   13   8
    

    在上面的树中,如果您希望删除节点 20,那么您将其与节点 30(节点 35 的子节点)交换:

                30
         40            15
      60    35     18       10
    80    38  20 19  17   13   8
    

    然后将节点20 删除为无子节点。如果没有节点30,那么左子树最右边的子节点将是节点35,你可以用它交换节点20。然后你必须删除一个只有一个子节点的节点,你知道该怎么做。

    请注意,在您完成删除之前,树处于不一致状态。如果您同时工作,这一点很重要。

    【讨论】:

    • 是的,这正是我正在做的......我的问题更多的是关于如何删除对节点的引用,一旦它被删除。我没有搜索他的父母,而是传递了对节点指针的引用,然后删除了该节点,并将其值设置为 0。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多