定义
二叉查找树(Binary Search Tree),也称二叉搜索树,是指一棵空树或者具有下列性质的二叉树:
- 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
二叉查找树相比于其他数据结构的优势在于查找、插入的时间复杂度较低。为O(log n)。二叉查找树是基础性数据结构,用于构建更为抽象的数据结构,如集合、multiset、关联数组等。
二叉查找树是基于二叉树的,其结点数据结构定义为如下:
private class Node {
private Key key;
private Value val; // 存储的数据
private Node left, right; // 左或右子树
private int size; // 子树的节点数
public Node(Key key, Value val, int size) {
this.key = key;
this.val = val;
this.size = size;
}
}
查找操作
在二叉查找树中查找x的过程如下:
1、若二叉树是空树,则查找失败。
2、若x等于根结点的数据,则查找成功,否则。
3、若x小于根结点的数据,则递归查找其左子树,否则。
4、递归查找其右子树。
根据上述的步骤,写出其查找操作的代码:
public boolean contains(Key key) {
if (key == null) throw new IllegalArgumentException("argument to contains() is null");
return get(key) != null;
}
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key key) {
if (key == null) throw new IllegalArgumentException("called get() with a null key");
if (x == null) return null;
int cmp = key.compareTo(x.key);
if (cmp < 0) return get(x.left, key);//递归进左子树查找
else if (cmp > 0) return get(x.right, key);//递归进右子树查找
else return x.val;//查找命中
}
插入操作
二叉树查找树b插入操作x的过程如下:
1、若b是空树,则直接将插入的结点作为根结点插入。
2、x等于b的根结点的数据的值,则直接返回,否则。
3、若x小于b的根结点的数据的值,则将x要插入的结点的位置改变为b的左子树,否则。
4、将x要出入的结点的位置改变为b的右子树。
代码如下:
public void put(Key key, Value val) {
if (key == null) throw new IllegalArgumentException("calledput() with a null key");
if (val == null) {
delete(key);
return;
}
root = put(root, key, val);
assert check();
}
private Node put(Node x, Key key, Value 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.size = 1 + size(x.left) + size(x.right);
return x;
}
删除操作
二叉查找树中最难实现的就是删除操作。下面是一个解决办法:
在删除节点x后,用他的后继节点填补它的位置。步奏如下:
1,将指向即将被删除的节点的链接保存为t;
2,将x指向它的后继节点min(t.right);
3,将x的右链接指向deleteMin(t.right);
4,将x的左链接(本为空)设为t.left。
代码如下:
//删除最小节点(热身)
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMin(root);
}
private Node deleteMin(Node x) {
if (x.left == null) return x.right;//不断检索左子树,直到遇到空的左链接,返回右链接
x.left = deleteMin(x.left);
return x;
}
//删除最大节点(热身)
public void deleteMax() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMax(root);
}
private Node deleteMax(Node x) {
if (x.right == null) return x.left;//不断检索右子树,直到遇到空的右链接,返回左链接
x.right = deleteMax(x.right);
return x;
}
//删除任意节点
public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("called delete() with a null 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指向它的后继节点
x.right = deleteMin(t.right);//将x的右链接指向删除后所有节点都大于x.key的子二叉查找树
x.left = t.left;
}
return x;
}
public Key min() {
if (isEmpty()) throw new NoSuchElementException("called min() with empty symbol table");
return min(root).key;
}
private Node min(Node x) {
if (x.left == null) return x;
else return min(x.left);
}