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