Binary Search Trees
1 BSTs 二叉查找树
- no more tree (search miss)
package Chapter03;
//基于二叉查找树的符号表
public class BST<Key extends Comparable<Key>, Value>{
private Node root; //二叉查找树的根结点
//内部类
private class Node{
private Key key;
private Value val;
private Node left, right; //指向子树的链接
//构造器
public Node(Key key, Value val){
this.key = key;
this.val = val;
}
}
public void put(Key key, Value val){
//查找key,找到则更新它的值,否则为它创造一个新的结点
root = put(root, key, val);//root根结点
}
//cost: number of compare is equal to 1 + depth of node
private Node put(Node x, Key key, Value val){
//如果key存在于以x为根结点的子树中则更新它的值,否则将以key和val为键值对的新结点插入到该子树中
if (x == null) return new Node(key,val);
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = put(x.left,key,val);//递归
else if (cmp > 0) x.right = put(x.right,key,val);
else x.val = val;
return x;//最终回到一个node
}
//return value corresponding to given key, or null if no such key
//cost: number of compare is equal to 1 + depth of node
public Value get(Key key){
Node x = root;
while (x != null){
int cmp = key.compareTo(x.key); //key和key比较
if (cmp < 0) x = x.left;
else if (cmp > 0) x = x.right;
else return x.val;
}
return null;
}
}
- worst case和链表一样,按字母从小到大的顺序输入的
2 ordered operations in BSTs 有序性相关方法
2.1 最大键和最小键
- 从root开始一直往右,直到找到一个null的右链接,就找到了最大键;同理,一直向左,可找到最小键
public Key min(){
return min(root).key;
}
private Node min(Node x){
if (x.left == null) return x;
return min(x.left);
}
- max()将left和right调换
2.2 向上取整和向下取整
//寻找小于等于key的最大的Key
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);
//cmp > 0
Node t = floor(x.right,key);
if (t != null) return t;//在x.right子树中找到一个node
else return x;//如果是空,回到上一级Key,上一级Key即为最终结果,说明右边子树不符合小于等于key的条件
}
- 把所有的左变右(同时将小于变成大于)就能得到ceiling()的算法
2.3 排名
2.3.1 size()
package Chapter03;
//基于二叉查找树的符号表
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 count;//number of nodes in subtrees
//构造器
public Node(Key key, Value val, int count){
this.key = key;
this.val = val;
this.count = count;
}
}
public void put(Key key, Value val){
//查找key,找到则更新它的值,否则为它创造一个新的结点
root = put(root, key, val);//root根结点
}
//cost: number of compare is equal to 1 + depth of node
private Node put(Node x, Key key, Value val){
//如果key存在于以x为根结点的子树中则更新它的值,否则将以key和val为键值对的新结点插入到该子树中
if (x == null) return new Node(key,val,1);
int cmp = key.compareTo(x.key);
if (cmp < 0) x.left = put(x.left,key,val);//递归
else if (cmp > 0) x.right = put(x.right,key,val);
else x.val = val;
x.count = 1 + size(x.left) + size(x.right);//递归
return x;//最终回到一个node
}
public int size(){
return size(root);
}
private int size(Node x){
if (x == null) return 0;
return x.count;
}
}
2.3.2 rank()
- how many keys < k ?
public int rank(Key key){
return rank(key, root);
}
private int rank(Key key, Node x){
//返回以x为根结点的子树中小于x.key的键的数量
if (x == null) return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0) return rank(key, x.left);//若key小于根结点,返回key在左子树的排名(递归)
else if (cmp > 0) return 1 + size(x.left) + rank(key, x.right);
else return size(x.left);//如果key和根结点的key相等,rank就是左边子树的结点总数
}
2.3.3 中序遍历
遍历过程如下:
- 访问左子树,
- 访问根节点,
- 访问右子树。
- 队列(queue)是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作。
//中序遍历:迭代
public Iterable<Key> keys(){
Queue<Key> q = new Queue<Key>();
inorder(root, q);
return q;
}
private void inorder(Node x, Queue<Key> q){
if (x == null) return;
inorder(x.left, q);//递归
q.enqueue(x.key);//把根结点加入队列
inorder(x.right, q);
}
2.3.4 summary
3 deletion 删除操作
- tombstone 墓碑
- 如果有大量的删除操作,内存过载
3.1 删除最小键
//删除最小键
public void deleteMin(){
root = delete(root);
}
private Node deleteMin(Node x){
if (x.left == null) return x.right;
x.left = deleteMin(x.left);//x.left被重新赋值,新的left是原来的left的right
x.count = size(x.left) + size(x.right) + 1;
return x;
}
3.2 Hibbard deletion
//删除键值对
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;//no right child
if (x.left == null) return x.right;//no left child
//replace with successor,有两个子树的情况
Node t = x;//x为即将被删除的结点
x = min(t.right);//x更新为x的右子树的最小结点,作为新"根"结点
x.right = deleteMin(t.right);//将x的右链接
x.left = t.left;//把t的左子树安装到后继结点(新x)的左边
}
x.count = size(x.left) + size(x.right) + 1;//update subtree counts
return x;
}
- 删除会使得二叉树变得不对称
- after a sequence of insert and delete, 树的高度变为square root of N --> 根号N,比期待的logN大很多