删除了结点,而让这棵树变得不满足二叉排序树的特性,所以删除需要考虑多种情况。
如果需要查找并删除在二叉排序树中是叶子的结点,那是很容易的,毕竟删除它们对整棵树来说,其他结点的结构并未受到影响
对于要删除的结点只有左子树或只有右子树的情况,相对也比较好解决。那就是结点删除后,将它的左子树或右子树整个移动到删除结点的位置即可,可以理解为独子继承父业。
但是对于要删除的结点既有左子树又有右子树的情况怎么办呢?
我们对删除结点三种情况进行分析:
叶子结点;
仅有左或右子树的结点;
左右子树都有的结点,我们来看代码,下面这个算法是递归方式对二叉排序树T查找key,查找到时删除。
/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素结点, */
/* 并返回TRUE;否则返回FALSE */
Status DeleteBST(BiTree *T, int key)
{
/* 不存在关键字等于key的数据元素 */
if (!*T)
return FALSE;
else
{
/* 找到关键字等于key的数据元素 */
if (key == (*T)->data)
return Delete(T);
else if (key < (*T)->data)
return DeleteBST(&(*T)->lchild, key);
elsereturn DeleteBST(&(*T)->rchild, key);
}
}
这段代码和前面的二叉排序树查找几乎完全相同,唯一的区别就在于第8行,此时执行的是Delete方法,对当前结点进行删除操作。我们来看Delete的代码。
/* 从二叉排序树中删除结点p,并重接它的左或右子树。 */
Status Delete(BiTree *p)
{
BiTree q, s;
/* 右子树空则只需重接它的左子树 */
if ((*p)->rchild == NULL)
{
q = *p;
*p = (*p)->lchild;
free(q);
}
/* 只需重接它的右子树 */
else if ((*p)->lchild == NULL)
{
q = *p;
*p = (*p)->rchild;
free(q);
}
/* 左右子树均不空 */
else
{q = *p;
s = (*p)->lchild;
/* 转左,然后向右到尽头(找待删结点的前驱) */
while (s->rchild)
{q = s;
s = s->rchild;
}
/* s指向被删结点的直接前驱 */
(*p)->data = s->data;
if (q != *p)
/* 重接q的右子树 */
q->rchild = s->lchild;
else
/* 重接q的左子树 */
q->lchild = s->lchild;
free(s);
}
return TRUE;
}
这段代码实现了从二叉排序树中删除指定结点*p的功能。具体的实现逻辑如下:
1. 首先判断*p的右子树是否为空,如果为空,则只需要将*p的左子树重接到*p的位置上,并释放*p的内存空间,完成删除操作。
2. 如果*p的右子树不为空,再判断*p的左子树是否为空。如果左子树为空,则只需要将*p的右子树重接到*p的位置上,并释放*p的内存空间,完成删除操作。
3. 如果*p的左右子树均不为空,先找到*p的直接前驱结点s。具体的查找方法是从*p的左子树开始,沿着右孩子一直向右走,直到找到一个结点s,该结点没有右孩子。
4. 找到直接前驱结点s后,将其数值复制给*p,然后再将s的左子树重接到s的父结点上。具体的处理分两种情况:
- 如果s是*q的右孩子,则将s的左子树重接到*q的右孩子上。
- 如果s是*q的左孩子,则将s的左子树重接到*q的左孩子上。
5. 最后释放结点s的内存空间,完成删除操作。
需要注意的是,这段代码只实现了删除结点*p,并未进行二叉排序树的调整,即删除结点后的二叉排序树可能不满足二叉排序树的性质。如果需要保持二叉排序树的性质,还需要进行相应的调整操作。
从这段代码也可以看出,我们其实是在找删除结点的前驱结点替换的方法,对于用后继结点来替换,方法上是一样的。