这篇文章主要介绍BST的删除操作,二叉查找树中最难实现的方法是delete()
。先从简单操作入手,考虑deleteMin()
和deleteMax()
。
01 删除最小键和删除最大键
最大键和最小键有个特征是它们要么只有一个子节点要么没有子节点。
根据二叉查找树的性质:每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键。
左节点<根节点<右节点
二叉查找树中最左边的结点是这棵树最小的结点。
对于deleteMin()
,只需不断深入根节点的左子树中直至遇见一个空链接,然后将指向该节点的链接指向该节点的右子树。
上图摘自《算法》第四版,Robert Sedgewick
Node* deletemin(Node* x)
{
if (x->_left == nullptr) return x->_right;
x->_left = deletemin(x->_left);
x->_nodeCnt = size(x->_left) + size(x->_right) + 1;
return x;
}
上图中删除最小键A的递归过程如下:
deletemax()
操作同理,只需要将x->left
替换为x->right
。
02 删除包含两个子结点的键
包含最大键或者最小键的节点要么只有一个子节点,要么没有子节点,删除这类节点都可以按照上述方式。但如何删除一个拥有两个子节点的节点呢?
T.Hibbard在1962年提出了解决这个难题的第一个方法:在删除结点x后,用它的后继结点填补它的位置。分为如下4个简要的步骤:
- 将指向即将被删除的结点的链接保存为t;
- 将x指向它的后继结点min(t.right);
- 将x的右链接(原本指向一颗所有节点都大于x.key的二叉查找树)指向
deleteMin(t.right)
,也就是在删除后所有节点仍然都大于x.key的子二叉查找树; - 将x的左链接(原本为空)设为t.left(其下所有的键都小于被删除的节点和它的后继结点)。
上图摘自《算法》第四版,Robert Sedgewick
Node* deleteNode(Node* x, const K& key)
{
if (x == nullptr) return nullptr;
int cmp = compareTo(key, x->_key);
if (cmp < 0)
{
x->_left = deleteNode(x->_left, key);
}
else if (cmp > 0)
{
x->_right = deleteNode(x->_right, key);
}
else
{
if (x->_right == nullptr) return x->_left;
if (x->_left == nullptr) return x->_right;
Node t = x; //保存为t
x = minNode(t._right); //x指向t的后继结点
x->_right = deletemin(t._right);
x->_left = t._left;
}
x->_nodeCnt = size(x->_left) + size(x->_right) + 1;
return x;
}