二叉查找树
二叉查找树,也称为二叉排序树,二叉搜索树。
二叉查找树结合了链表插入的灵活性和有序数组查找(二分查找)的高效性。
用二叉查找树实现有序符号表的API。
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;
this.N = N;
}
}
//有序符号表的相关方法
//...
//...
}
- size()方法
返回以该结点为根的子树中结点总数。
public int size() {
return size(root);
}
private int size(Node x) {
if(x == null) return 0;
else return x.N; //直接返回该结点的N
}
- get()方法
查找键对应的值
public Value get(Key key) {
return get(root, key);
}
/**
* 查找操作
* 在以x为根的子树中查找键对应的值。
* 这里采用递归方式。
*/
private Value get(Node x, Key key) {
if(x == null) //未找到key,返回null
return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) //key < x.key
return get(x.left, key); //在左子树中查找
else if (cmp > 0) //key > x.key
return get(x.right, key); //在右子树中查找
else //key = x.key
return x.val; //返回该键对应的值
}
- put()方法
插入键值对
public void put(Key key, Value val) {
root = put(root, key, val);
}
/**
* 插入操作
* 如果key存在于以x为根的子树中,更新它的值。
* 如果key不存在,插入该键值对。
* 每查找到一个结点都要更新它的规模信息和左(右)链接信息。
* 这里采用递归方式。
*/
private Node put(Node x, Key key, Value val) {
if(x == null) //如果不存在key
return new Node(key ,val,1); //返回新结点
int cmp = key.compareTo(x.key);
if (cmp < 0) //key < x.key
x.left = put(x.left, key, val); //在左子树查找,并将该结点左链接置为返回的结点
else if (cmp > 0) //key > x.key
x.right = put(x.right, key, val); //不这样赋值的话当前结点就无法指向新插入的孩子结点(如果它的孩子结点是待插入结点)
else //key = x.key
x.key = key;
x.N = size(x.left) + size(x.right) + 1; //更新当前结点的N
return x;
}
- select()方法
查找并返回排名为k的键
/**
* 查找并返回排名为k的键(即树中正好有k个小于它的键)
*/
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) //排名为k的结点位于左子树中
return select(x.left, k);
else if (t < k) //排名为k的结点位于右子树中
return select(x.right, k-t-1);
//排名k减去左子树规模和当前结点
else //当前结点排名为k
return x;
}
- rank()方法
查找并返回键的排名
/**
* 查找并返回键为key的排名
*/
public int rank(Key key) {
return rank(root, key);
}
private int rank(Node x, Key key) {
if (x == null)
return 0;
int cmp = key.compareTo(x.key);
if (cmp < 0) //位于左子树中
return rank(x.left, key);
else if (cmp > 0) //位于右子树中
return size(x.left) + rank(x.right, key) + 1;
//要加上左子树结点数和当前结点
else
return size(x.left);
}
- min()方法
返回最小的键
返回最大的键max()方法类似
/**
* 返回最小的键
*/
public Key min() {
return min(root).key;
}
private Node min(Node x) {
if(x.left == null)
return x;
return min(x.left);
}
- deleteMin()方法
删除最小结点
删除最大结点deleteMax()方法类似
/**
* 删除最小节点
*/
public void deleteMin() {
root = deleteMin(root);
}
private Node deleteMin(Node x) {
if (x.left == null) //x结点是最小结点
return x.right;
//返回当前结点右结点(x的父结点左链接将指向x.right)
//x将被gc回收
x.left = deleteMin(x.left);
x.N = size(x.left) + size(x.right) + 1;
//更新结点规模
return x;
}
- delete()方法
删除一个结点可以将当前结点x替换为右子树中最小的结点。
具体删除一个结点的步骤: - 1、将即将删除的结点的链接保存为t
- 2、x指向它的后继结点min(t.right)
- 3、x的右链接指向deleteMin(t.right) (详见deleteMin()方法)
- 4、x的左链接指向t.left
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)
return x.left = delete(x.left, key);
else if (cmp > 0)
return x.right = delete(x.right, key);
else { //当前结点是被删除结点
if (x.right == null) //右子树为空
return x.left;
if (x.left == null) //左子树为空
return x.right;
Node t = x; //步骤1
x = min(t.right); //步骤2
x.right = deleteMin(t.right); //步骤3
x.left = t.left; //步骤4
}
x.N = size(x.left) + size(x.right) + 1;
//更新当前结点的规模
return x;
}
- keys()方法
用到了队列
/**
* 二叉查找树的范围查找
*/
public Iterable<Key> keys() {
return keys(min(), max());
}
public Iterable<Key> keys(Key lo, Key hi) {
Queue<Key> queue = new LinkedList<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.add(x.key);
if (cmphi > 0)
keys(x.right, queue, lo, hi);
}