查找算法【二叉查找树】 - 二叉查找树的删除
首先要在二叉查找树中找到待删除节点,然后执行删除操作。
假设指针p 指向待删除节点,指针f 指向p 的双亲节点。根据待删除节点所在位置的不同,删除操作的处理方法也不同,可分为下面三种情况。
① 被删除节点的左子树为空。如果被删除节点的左子树为空,则令其右子树子承父业代替其位置即可。
例如,在二叉查找树中删除P节点,如下图所示。
② 被删除节点的右子树为空。
如果被删除节点的右子树为空,则令其左子树子承父业代替其位置即可,如下图所示。
③ 被删除节点的左右子树均不为空。
如果被删除节点的左子树和右子树均不为空,则无法再使用子承父业的方法了。根据二叉查找树的中序有序性,删除该节点时,可以用其直接前驱(或直接后继)代替其位置,然后删除其直接前驱(或直接后继)即可。
那么在中序遍历序列中,一个节点的直接前驱(或直接后继)是哪个节点呢?
直接前驱: 在中序遍历中,节点P 的直接前驱为其左子树的最右节点。即沿着P 的左子树一直访问其右子树,直到没有右子树,就找到了最右节点,如下图(a )所示。
直接后继: 在中序遍历中,节点P 的直接后继为其右子树的最左节点。如图(b )所示,s 指向p 的直接后继,q 指向s 的双亲。f 、p、q、s 为指向节点的指针,也可以代指该节点。
以p 的直接前驱s 代替p 为例,相当于把s 的数据赋值给p ,即s代替p ,然后删除s 即可,因为s 为最右节点,它没有右子树,删除后,左子树子承父业代替s ,如下图所示。
例如,在二叉查找树中删除24。
首先查找到24的位置p ,然后找到p 的直接前驱s (22),把22赋值给p 的数据域,删除s ,删除过程如下图所示。
删除节点之后是不是仍然满足二叉查找树的中序遍历有序性?
需要注意的是,有一种特殊情况,即p 的左孩子没有右子树,s 就是其左子树的最右节点(直接前驱),即s 代替p ,然后删除s 即可,因为s 为最右节点且没有右子树,删除后,左子树子承父业代替s 。如下图所示。
例如,在二叉查找树中删除20,删除过程如下图所示。
【算法步骤】
① 在二叉查找树中查找待删除关键字的位置,p 指向待删除节点,f 指向p 的双亲节点,如果查找失败,则返回。
② 如果查找成功,则分三种情况进行删除操作。
- 如果被删除节点的左子树为空,则令其右子树子承父业代替其位置即可。
- 如果被删除节点的右子树为空,则令其左子树子承父业代替其位置即可。
- 如果被删除节点的左右子树均不为空,则令其直接前驱(或直接后继)代替它,再删除其直接前驱(或直接后继)。
【举个栗子】
① 左子树为空。在二叉查找树中删除32,首先查找到32所在的位置,判断其左子树为空,则令其右子树子承父业代替其位置。删除过程如下图所示。
② 右子树为空。在二叉查找树中删除69,首先查找到69所在的位置,判断其右子树为空,则令其左子树子承父业代替其位置。删除过程如下图所示。
③ 左右子树为不空。在二叉查找树中删除25,首先查找到25所在的位置,判断其左右子树均不为空,则令其其直接前驱(左子树最右节点20)代替它,再删除其直接前驱20即可。删除20时,其左子树代替其位置。删除过程如下图所示。
【算法实现】
void DeleteBST(BSTree &T , char key){
//从二叉查找树T中删除关键字等于key 的节点
BSTree p = T;
BSTree f = NULL;
BSTree q , s;
if(!T){
return; //树为空则返回
}
while(p){ //查找
if(p->data == key){
break; //找打关键字等于key的节点p,结束循环
}
f = p; //f为p 的双亲
if(p->data > key){
p = p->lchild; //在p的左子树中继续查找
}
else{
p = p->rchild; //在p 的右子树中继续查找
}
}
if(!p){
return; //找不到被删除节点则返回
}
//三种情况:p左右子树均不为空、无右子树、无左子树
if((p->lchild) && (p->rchild)){ //被删除节点p 的左右子树均不为空
q = p;
s = p->lchild;
while(s->lchild){ //在p 的左子树中查找p的前驱s,即最右下节点
q = s;
s = s->rchild;
}
p->data = s->data; //将s 的值赋给被删除节点p,然后删除s
if(q != p){
q->rchild = s->lchild; //重接q 的右子树
}
else{
q->lchild = s->lchild; //重接q 的左子树
}
delete s;
}
else{
if(!p->rchild){ //被删除节点p 无右子树,只需重接其左子树
q = p;
p = p->lchild;
}
else if(!p->lchild){ //被删除节点p 无左子树,只需重接其右子树
q = p;
p = p->rchild;
}
//将p所指的子树挂接到其双亲f 相应的位置
if(!f){
T = p; //被删除节点为根节点
}
else if(q == f->lchild){
f->lchild = p; //挂接到f 的左子树位置
}
else{
f->rchild = p; //挂接到f 的右子树位置
}
delete q;
}
}
【算法分析】
二叉查找树的删除主要是查找的过程,需要O (logn )时间。在删除过程中,如果需要查找被删除节点的前驱,则也需要O(logn )时间。所以,在二叉查找树中进行删除操作的时间复杂度为O(logn )。