C++ 从二叉搜索树中删除后子节点分配的原因?

C++ 从二叉搜索树中删除后子节点分配的原因?,c++,algorithm,data-structures,tree,binary-search-tree,C++,Algorithm,Data Structures,Tree,Binary Search Tree,我在学习BST删除机制时,有一点我还没有理解。你能解释一下为什么每次调用Delete(Node*p,int键)时都会有赋值(p->rchild=,p->lchild=)吗?实际上,我认为Delete(Node*p,int-key)方法只是不断返回,没有任何变异,所以树不会改变 当我在寻找解释的时候,我无意中发现了这句话: 我们必须在删除后进行分配,否则我们最终会有 重复节点 如果你同意这个说法,你能给我解释一下吗 Node* BST::Delete(Node *p, int key) {

我在学习BST删除机制时,有一点我还没有理解。你能解释一下为什么每次调用Delete(Node*p,int键)时都会有赋值(p->rchild=,p->lchild=)吗?实际上,我认为Delete(Node*p,int-key)方法只是不断返回,没有任何变异,所以树不会改变

当我在寻找解释的时候,我无意中发现了这句话:

我们必须在删除后进行分配,否则我们最终会有 重复节点

如果你同意这个说法,你能给我解释一下吗

Node* BST::Delete(Node *p, int key) {
    Node* q;
 
    if (p == nullptr){
        return nullptr;
    }
 
    if (p->lchild == nullptr && p->rchild == nullptr){
        if (p == root){
            root = nullptr;
        }
        delete p;
        return nullptr;
    }
 
    if (key < p->data){
        p->lchild = Delete(p->lchild, key);
    } else if (key > p->data){
        p->rchild = Delete(p->rchild, key);
    } else {
        if (Height(p->lchild) > Height(p->rchild)){
            q = InPre(p->lchild);
            p->data = q->data;
            p->lchild = Delete(p->lchild, q->data);
        } else {
            q = InSucc(p->rchild);
            p->data = q->data;
            p->rchild = Delete(p->rchild, q->data);
        }
    }
    return p;
}
Node*BST::Delete(Node*p,int键){
节点*q;
如果(p==nullptr){
返回空ptr;
}
如果(p->lchild==nullptr&&p->rchild==nullptr){
if(p==根){
root=nullptr;
}
删除p;
返回空ptr;
}
如果(键数据){
p->lchild=删除(p->lchild,键);
}否则如果(键>p->数据){
p->rchild=Delete(p->rchild,键);
}否则{
if(高度(p->lchild)>高度(p->rchild)){
q=InPre(p->lchild);
p->data=q->data;
p->lchild=Delete(p->lchild,q->data);
}否则{
q=InSucc(p->rchild);
p->data=q->data;
p->rchild=Delete(p->rchild,q->data);
}
}
返回p;
}
为什么每次调用
Delete(Node*p,int键)
时都会有赋值(
p->rchild=
p->lchild=

如果找到了数据,那么目标是使树少一个节点。该算法将使用值交换机制,以确保实际要删除的节点始终是叶节点。删除叶节点包括两个操作:

  • 从记忆中删除
  • 其父节点的更新,以便引用此已删除节点的子指针设置为空指针
  • 由于该算法直接递归到该叶节点,因此无法将空指针设置到其父节点,因为在此阶段父节点没有可用的引用。为此,调用方应该做一些事情,因为调用方确实有对父对象的引用

    因此,当递归遍历到达叶节点时,它需要向调用者传达应该分离该节点的信息。它通过返回一个空指针来实现,协议是调用方(其当前节点
    p
    是父节点)应该将返回的指针分配给相关的子指针。这样,被删除的节点就与树的其余部分真正分离了

                     7
                    / \
                   4*  8
                  / \   \
                 1   5   9
                /   / \ 
               0   4*  6
    
    实际上,我认为
    Delete(Node*p,int-key)
    方法只是不断返回,没有任何变异,所以树不会改变

    当然,树必须以某种方式更改,以便从中删除一个节点。更改发生在分配给
    p->lchild
    p->rchild

    我无意中说出了这句话:

    我们必须在删除后进行分配,否则将导致重复节点

    这是真的。让我们以一棵树为例:

                     7
                    / \
                   3   8
                  / \   \
                 1   5   9
                /   / \ 
               0   4   6
    
    现在让我们看看调用
    Delete(root,3)
    会发生什么
    p
    指向值为7的节点。我们通过递归调用转到左侧:

    p->lchild = Delete(p->lchild, key);
    
    在递归执行上下文中,我们得到一个新的
    p
    ,它指向值为3的节点。这是我们正在寻找的值,因此我们进入外部
    else
    块。由于该节点下方子树的高度相等,我们进入内部
    else
    块。在这里,我们分配:

    q = InSucc(p->rchild);
    
    q
    将引用值为4的节点。现在发生了重复。我们将数据从
    q
    复制到
    p
    。这可以归结为从树中删除值3:

    p->data = q->data;
    
    但现在树中的值是4的两倍

                     7
                    / \
                   4*  8
                  / \   \
                 1   5   9
                /   / \ 
               0   4*  6
    
    因此,算法下降到(右)子级,现在试图删除该子树中值为4的节点:

    p->rchild = Delete(p->rchild, q->data);
    
    在这个新的递归调用中,我们再次得到一个新的
    p
    ,它现在引用值为5的节点。我们向左转——这项任务将在以后发挥重要作用:

    p->lchild = Delete(p->lchild, key);
    
    最后一个递归调用有一个新的
    p
    ,它引用了值为4的节点——我们正在寻找的节点

    这一次,我们将在
    if
    块中结束,该块具有
    delete
    ,因为该节点是叶节点。节点被释放,一个空指针返回给调用方。从这里开始,我们开始回溯到树上

                     7
                    / \
                   4*  8
                  / \   \
                 1   5   9
                /   / \ 
               0   4*  6
    
    因此,在上一级,在值为5的节点上,我们从递归调用(一个空指针)中获取返回值并分配它:

    p->lchild = Delete(p->lchild, key);
    
    此重要赋值将从树中分离重复节点(值为4)。您可以看到,如果不进行此赋值,则仍然会有一个对该节点的引用,该引用具有重复的值——即使它指向已释放的内存

    该树现在处于最终形状:

                     7
                    / \
                   4   8
                  / \   \
                 1   5   9
                /     \ 
               0       6
    
    回溯仍将继续,返回到根。也有一些子指针的赋值,但这些不会改变树,因为在所有这些情况下,我们都返回了
    returnp,它是调用方子指针的原始值

    缺陷 正如注释中提到的,代码有一个bug。删除叶节点时,不会验证此节点是否实际具有要删除的值。因此,如果您使用一个在树中没有出现的值调用此方法,您将最终删除一个具有另一个值的叶节点。在上面的示例树中:如果要调用
    Delete(root,10)
    ,则值为9的节点将被删除

    要更正此错误,请移动以下
    if
    块:

    if (p->lchild == nullptr && p->rchild == nullptr){
    
    。。。在外部的
    else
    块中,作为第一条语句。

    无关:
    如果(p->lchild==nullptr&&p->rchild==nullptr)
    看起来像是如果你到达了行的末尾,你