二叉查找树是一种能够将链表插入的灵活性和有序数组查找的高效性结合起来的符号表实现。就是使用每个节点含有两个链接(链表每个节点含有一个)的二叉查找树来高效地实现符号表,是很重要的算法。
一颗二叉查找树(BST)是一颗二叉树,其中每个节点都含有一个Comparable的键(以及相关联的值)且每个节点的键都大于其左子树中任意节点的键而小于右子树任意节点的键。
1.1 基本实现
以下是BST的数据结构:
public class BST<Key extends Comparable<Key>, Value> {
public Node root;
private class Node {
private Key key;
private Value val;
private Node left, right;
private int N;
public Node(Key key, Value val, int N) {
this.key = key;
this.val = val;
this.N = N;
}
}
public int size(){
return size(root);
}
private int size(Node x) {
if (x == null) {
return 0;
} else {
return x.N;
}
}
}
1.1.1 数据表示
size(x) = size(x.left) + size(x.right) + 1
1.1.2 查找
在符号表里查找一个键可能得到两个结果。如果含有该键的节点存在于表中,查找就命中了,返回相应的值,否则查找未命中(返回null)。可在二叉查找树中写一个递归算法:如果树是空的,则查找未命中;如果被查找的键与根节点的键相等,查找命中,否则就递归地在适当的子树中查找。如果被查找的键较小就选择左子树,否则就选择右子树。
1.1.3 插入
与查找类似。需要引起注意的是,二叉查找树是符号表实现,如果发现插入的元素已经存在,则不进行插入。
1.2 分析
使用二叉查找树的算法的运行时间取决于树的形状,树的形状又取决于键被插入的先后顺序。在最好的情况下,一棵含有N个节点的树是完全平衡的,每条空链接和跟节点的距离都会~logN。在最坏的情况下,搜索路径上可能有N个节点。但一般情况下树的形状与最好情况更洁净。
在由N个随机键构造的二叉查找树中,查找命中平均所需的比较次数为~2lnN。
在由N个随机键构造的二叉查找树中插入操作和查找未命中平均所需的比较次数为~2lnN。
1.3 有序性相关的方法与删除操作
1.3.1 最大键和最小键
如果根节点的左链接为空,那么一颗二叉查找树中最小的键就是根节点;如果左链接非空,那么树中的最小键就是左子树中的最小键。右子树与此类似。
1.3.2 向上取整和向下取整
如果给定的key小于二叉查找树的根节点的键,那么小于等于key的最大键一定在根节点的左子树中;如果给定的键key大于二叉查找树的根节点,那么只有当根节点右子树中有小于key的节点时,小于等于key的最大键才会出现的右子树中,否则根节点就是小于等于key的最大键。这就是floor方法。同理可知ceiling方法。
1.3.3 选择操作
如果想找到排名为k的键(即树中正好有k个小于它的键)。如果左子树中的结点数t大于k,那么就递归地在左子树中查找排名为k的键;如果t等于k,返回根节点中的键;如果t小于k,就递归地在右子树中查找排名为k-t-1的键。
1.3.4 排名
rank()是select()的逆方法,它会返回给定键的排名。如果给定的键和根节点相等,返回左子树中的节点总数t;如果给定的键小于根节点,递归计算其在左子树中的排名;如果给定的键大于根节点,会返回t+1加它在右子树中的排名。
1.3.5 删除最大键和最小键
对于deleteMin(),要不断深入根节点的左子树,直至遇见一个空链接,然后将指向该节点的链接指向该节点的右子树。deleteMax()方法的实现类似。
1.3.6 删除操作
当删除的节点有两个子节点时,步骤如下:
将指向即将被删除的节点的链接保存为t。
将x指向它的后继节点min(t.right)。
将x的右链接指向deleteMin(t.right),也就是删除后所有节点仍然都大于x.key的子二叉查找树。
将x的左链接设为t.left.
private Node delete(Node root, 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;
}
1.3.7 范围查找
public Iterable<key> keys() {
return keys(min(), max());
}
public Iterable<Key> keys(Key lo, Key hi) {
Queue<key> queue = new Queue<key>();
keys(root, queue, lo, hi);
return queue;
}
private void keys(Node x, Queue<Key> Queue, Key lo, Key hi) {
if (x == null) {
return;
}
int cmplo = lo.compareTo(x.key);
int cmphi = hi.compareTo(x.key);
if (cmplo < 0) {
keys(x.left, queue, lo, hi);
}
if (cmplo <= 0 && cmphi >= 0) {
queue.enqueue(x.key);
}
if (cmphi > 0) {
keys(x.right, queue, lo, hi);
}
}
1.3.8 性能分析
在一棵二叉查找树中,所有操作在最坏情况下所需的时间都和树的高度成正比。