二叉查找树能够将链表插入的灵活性和有序数组查找的高效性结合起来。用二叉查找树来实现符号表是很高效的,因为在平均情况下它的查找和插入都是对数级别的(最坏情况下变为线性),二叉查找树的插入和查找操作用递归实现起来并不难,其删除操作显得复杂一下,本文主要探讨其删除操作。
数据结构
Node{
Node left,right; //左右结点
Key key; //符号表的键
Value val;//符号表的值
int N;//结点数 ==size(left)+size(right)+1
}
API
表1 二叉查找树实现符号表api
public class BST<Key,Value> |
|
int size(Node x) | 返回该树的结点数量 |
Node min(Node x) | 返回该树中最小结点 |
void deleteMin() | 删除最小结点 |
void delete(Key key) | 根据键值删除特定结点 |
|
|
删除最小结点
DeleteMin的代码如下:
public void deleteMin(){
root = deleteMin(root);
}
private Node deleteMin(Node x){
if(x.left==null) return x.right;
x.left=deleteMin(x.left);
x.N = size(x.left)+size(x.right)+1;
return x;
}
最小结点在一棵非空的二叉查找树中是一定存在的,我们的删除操作思路如下:
1 找到这个结点。沿结点一直向左子结点寻找,直到碰到null结点,那便找到了最小结点
2 返回最小结点的右链接,使得右链接指向最小结点的父链接,这样便没有链接指向最小结点,最小结点也就被删除了。
deleteMax的思路相同。
删除指定键的结点
删除指定键结点的思路和删除最小结点类似,但是比较麻烦的是
1 根据指定要删除的键不一定存在
2删除结点后,被删除结点的父结点只有一个链接而被删除结点可能有两个子结点
对于2,一种解决方法是将被删除结点的后继放到被删除的位置。
public void delete(Key key){
root = delete(root,key);
}
private Node delete(Node x,Key key){
if(x==null) return null;
int cmp=key.compareTo(x.key);
if(cmp<0) x.left = delete(x.left,key);//递归删除左
else if(cmp>0) x.right =delete(x.right,key);//递归删除右
else{
//删除并复位
if(x.right==null) return x.left;
if(x.left==null) return x.right;
//被删除结点有两科子树的情况
//将被删除结点的后继放在被删除结点的位置上,该后继为右子树的最小结点
Node t = x;
x = min(t.right);
x.right = deleteMin(t.right);
x.left=t.left;
}
x.N = size(x.left)+size(x.right)+1;
return x;
}
总的思路是这样的,我们将要删除的键和结点的键进行比较,如果小则向左,反之向右,直到找到要删除的键或者没有找到。
下图是删除过程的图示:
这样,我们一直删除某个键,然后一直用某个键的后继来填补,这样会导致一个问题,这棵树的平衡性遭到了破坏。另外的一个思路是用删除键的前趋来填补被删除结点,示例如下:
可以看到,这便是二叉查找树的一个缺点,我们无法保证树的平衡,解决方案是什么?我们可以使用红黑树或者avl树来保证树的平衡性。我觉得红黑树是一种很优美的数据结构,后面有机会的话会探究一下。
本文原创,转载请联系。