在上一篇关于二叉搜索树的笔记中,我自己实现了几个常用的有关BST的成员函数。其中删除指定结点的函数十分复杂,还要考虑被删结点的三种可能的情况。
其实,还有另外一种思路来实现删除结点这一操作。
假设我们要删除的结点是u,可以发现,如果被删结点u没有左子树,或者没有右子树,或者左右子树都没有的话,实际编码过程中,被释放的结点d就是结点u本身。
但是如果结点u的左右子树均存在,实际编码过程中,结点u改变的只是自身的关键值u->key,没有被真正释放。真正被释放的结点d是结点u的右子树中值最小的结点
(当然也可以是左子树中值最大的结点,完全取决与你想如何实现)。因此,我们把关注点从结点u是否存在左右子树,转移到真正被释放的结点d到底是什么。
这就是实现删除某一结点函数的新思路:
一、找到实际需要删除并释放的结点d
如果被删结点u没有左子树,或者没有右子树,或者左右子树都没有,d = u
如果被删结点u左右子树均存在,d = u的右子树中值最小的结点
二、找到结点d 的父结点f 与 d 的子结点s
三、重置结点f 的子结点
f == NULL , 说明结点d为根结点,根结点root = s(结点u左右子树均存在时,d 的父结点f 一定存在,不会有这种情况)
否则将f 的子结点变为 s
四、释放结点d
如果结点u左右子树均存在(等价于d != u), u->key = d->key
最后释放结点d
参考代码如下:
//删除指定结点
void DeleteNode(tree_node &root, tree_node u)
{
tree_node d; //结点d存放实际需要释放的结点
tree_node f = NULL; //结点f存放结点d的父结点
tree_node s; //结点s存放结点d的子结点
//确定结点d
if (u->left == NULL || u->right == NULL) d = u;
else {
d = u->right;
while (d->left != NULL) d = d->left;
}
//确定结点s
if (d->left != NULL) {
s = d->left;
} else {
s = d->right; //此时结点s也可能为NULL
}
//确定结点f
tree_node x = root;
while (x != NULL && x != d) {
f = x;
if (d->key < x->key)
x = x->left;
else
x = x->right;
}
if (x == NULL) return; //要删除的结点不存在
//删除并释放结点d
if (f == NULL) root = s; //可以证明,这种情况下,结点u不可能同时存在左右子树
else if (d == f->left)
f->left = s;
else
f->right = s;
if (d != u)
u->key = d->key; //结点u左右子树均存在
free(d);
return;
}