定义:一棵二叉查找树(BST),其中每个结点都含有一个Comparable的键(以及相关联的值)且每个结点的键都大于其左子树中的任意结点的键而小于右子树的任意结点的键。
二叉查找树是一种能够将链表插入的灵活性和有序数组查找的高效性结合起来的符号表实现。
基本实现
数据表示
和链表一样,我们嵌套定义了一个私有类来表示二叉查找树上的一个结点。每个结点都含有一个键、一个值、一条左链接、一条右链接和一个结点计数器。
查找
在二叉查找树中查找一个键的递归算法:如果树是空的,则查找未命中;如果被查找的键和根结点的键相等,查找命中,否则我们就(递归地)在适当的子数中继续查找。
和二分查找中每次迭代之后查找的区间就会减半一样,在二叉查找树中,随着我们不断向下查找,当前结点所表示的子树的大小也在减少。
对于命中的查找,路径在含有被查找的键的结点处结束。对于未命中的查找,路径的终点是一个空链接。
插入
递归插入的实现逻辑和递归查找很相似:如果树是空的,就返回一个含有该键值对的新结点;如果被查找的键小于根结点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。
最大键和最小键
如果根结点的左链接为空,那么一棵二叉查找树中最小的键就是根结点;如果左链接非空,那么树中的最小键就是左子树中的最小键。
向上取整和向下取整
如果给定的键key小于二叉查找树的根结点的键,那么小于等于key的最大键floor(key)一定在根结点的左子树中;如果给定的键key大于二叉查找树的根结点,那么只有当根结点右子树中存在小于等于key的结点时,小于等于key的最大键才会出现在右子树中,否则根结点就是小于等于key的最大键。
选择操作
假设我们想找到排名为k的键(即树中正好有k个小于它的键)。如果左子树中的结点数t大于k,那么我们就继续(递归地)在左子树中查找排名为k的键;如果t等于k,我们就返回根结点中的键;如果t小于k,我们就(递归地)在右子树中查找排名为(k-t-1)的键。
排名
如果给定的键和根结点的键相等,我们返回左子树中的结点总数t;如果给定的键小于根结点,我们会返回该键在左子树中的排名(递归计算);如果给定的键大于根结点,我们会返回t+1(根结点)加上它在右子树中的排名(递归计算)。
删除最大键和删除最小键
对于deleteMin(),我们要不断深入根结点的左子树中直至遇见一个空链接,然后将指向该结点的链接指向该结点的右子树。此时已经没有任何链接指向要被删除的结点,因此它会被垃圾收集器清理掉。
删除操作
T.Hibbbard在1962年提出了解决删除一个拥有两个子结点的结点这个难题的第一个方法,在删除结点x后用它的后继结点填补它的位置,因为x有一个右子结点,因此它的后继结点就是其右子树中的最小结点。我们用以下步骤完成将x替换为它的后继结点的任务:
- 将指向即将被删除的结点的链接保存为t;
- 将x指向它的后继结点min(t.right);
- 将x的右链接(原本指向一棵所有结点都大于x.key的二叉查找树)指向deleteMin(t.right),也就是在删除后所有结点仍然大于x.key的子二叉查找树;
- 将x的左链接(本为空)设为t.left(其下所有的键都小于被删除的结点和它的后继结点)。
在递归调用后我们会修正被删除的结点的父结点的链接,并将由此结点到根结点的路径上的所有结点的计数器减1。
范围查找
使用中序遍历。
性能分析
在一棵二叉查找树中,所有操作在最坏情况下所需的时间都和树的高度成正比。
总的来说,二叉查找树的实现并不困难,且当树的构造和随机模型近似时在各种实际应用场景中它都能进行快速地查找和插入。但同时,在某些场景中二叉查找树在最坏情况下的恶劣性能仍然是不可接受的。二叉查找树的基本实现的良好性能依赖于其中的键的分布足够随机以消除长路径。但最坏情况在实际应用也有可能出现,比如用例将所有键按照顺序或者逆序插入符号表就会增加这种情况出现的概率。
简单的符号表实现的成本总结
算法 | 最坏情况下的运行时间的增长数量级 | 平均情况下的运行时间的增长数量级 | 是否支持有序性相关的操作 |
---|
算法 | 查找 | 插入 | 查找命中 | 插入 | 是否支持有序性相关的操作 |
---|---|---|---|---|---|
顺序查找(无序链表) | N | N | N/2 | N | 否 |
二分查找(有序数组) | lgN | N | lgN | N/2 | 是 |
二叉树查找(二叉查找树) | N | N | 1.39lgN | 1.39lgN | 是 |
代码
package section3_1;
import java.util.ArrayList;
import java.util.List;
public class BST<Key extends Comparable<Key>,Value> {
private 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;
N = n;
}
}
public int size() {
return size(root);
}
public int size(Node x) {
if (x == null) return 0;
else return x.N;
}
public Value get(Key key) {
return get(root,key);
}
public Value get(Node x,Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp > 0) {
return get(x.right,key);
} else if (cmp < 0) {
return get(x.left,key);
} else {
return x.val;
}
}
public void put(Key key,Value value) {
root = put(root,key,value);
}
public Node put(Node x, Key key, Value value) {
if (x == null) return new Node(key,value,1);
int cmp = key.compareTo(x.key);
if (cmp < 0) {
x.left = put(x.left,key,value);
} else if (cmp > 0) {
x.right = put(x.right,key,value);
} else {
x.val = value;
}
x.N = size(x.left) + size(x.right) + 1;
return x;
}
public Key min() {
return min(root).key;
}
private Node min(Node x) {
if (x.left == null) return x;
return min(x.left);
}
public Key max() {
return max(root).key;
}
private Node max(Node x) {
if (x.right == null) return x;
return max(x.right);
}
public Key floor(Key key) {
Node x = floor(root,key);
if (x == null) {
return null;
}
return x.key;
}
private Node floor(Node x, Key key) {
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp == 0) return x;
if (cmp < 0) return floor(x.left,key);
Node t = floor(x.right,key);
if (t != null) return t;
else return x;
}
public Key select(int k) {
return select(root,k).key;
}
private Node select(Node x, int k) {
if (x == null) return null;
int t = size(x.left);
if (t > k) return select(x.left,k);
else if (t < k) return select(x.right,k-t-1);
else return x;
}
public int rank(Key key) {
return rank(key,root);
}
private int rank(Key key, Node x) {
if (x == null) return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0) return rank(key,x.left);
else if (cmp > 0) return 1 + size(x.left) + rank(key,x.right);
else return size(x.left);
}
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;
}
public void delete(Key key) {
root = delete(root,key);
}
public 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;
}
public Iterable<Key> keys() {
return keys(min(),max());
}
public Iterable<Key> keys(Key lo, Key hi) {
List<Key> list = new ArrayList<>();
keys(root,list,lo,hi);
return list;
}
private void keys(Node x, List<Key> list, 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,list,lo,hi);
if (cmplo <= 0 && cmphi >= 0) list.add(x.key);
if (cmphi > 0) keys(x.right,list,lo,hi);
}
public static void main(String[] args) {
int minlen = 1;
BST<String,Integer> bst = new BST<>();
String[] words = new String[]{
"it","was","the","best","of","times","it","was","the","worst","of","times",
"it","was","the","age","of","wisdom","it","was","the","age","of","foolishness",
"it","was","the","epoch","of","belief","it","was","the","epoch","of","incredulity",
"it","was","the","season","of","light","it","was","the","season","of","darkness",
"it","was","the","spring","of","hope","it","was","the","winter","of","despair"
};
int idx = 0;
while (idx < words.length) {
String word = words[idx];
if (word.length() < minlen) {
idx++;
continue;
}
if (bst.get(word) == null) {
bst.put(word,1);
} else {
bst.put(word,bst.get(word)+1);
}
idx++;
}
int len = 0;
for (String x:bst.keys()) {
System.out.println(x + " " + bst.get(x));
len++;
}
System.out.println(len);
}
}