出自《算法》
这部分《算法》里面写得很好,里面有一些操作是以前注意到的,特地整理下
涉及到BST的插入、删除
结构定义
private Node root; // root of BST
private class Node {
private Key key; // sorted by key
private Value val; // associated data
private Node left, right; // left and right subtrees
private int size; // number of nodes in subtree
public Node(Key key, Value val, int size) {
this.key = key;
this.val = val;
this.size = size;
}
}
书中用BST树实现了SearchTable,所以会有Key, Value的结构,重点关注size,key和val即可
size()
两个size函数,一个是返回整棵树的大小,另一个是返回以x为root的树的大小,后一个size函数很有用
/**
* Returns the number of key-value pairs in this symbol table.
*
* @return the number of key-value pairs in this symbol table
*/
public int size() {
return size(root);
}
// return number of key-value pairs in BST rooted at x
private int size(Node x) {
if (x == null) return 0;
else return x.size;
}
get()
既然是SearchTable,就会有get和put函数,下面是get函数
因为是BST,所以可以用性质来查找
/**
* Returns the value associated with the given key.
*
* @param key the key
* @return the value associated with the given key if the key is in the symbol table
* and {@code null} if the key is not in the symbol table
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public Value get(Key key) {
return get(root, key);
}
private Value get(Node x, Key key) {
if (key == null) throw new IllegalArgumentException("calls 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;
}
put()
然后是put函数,这个操作会影响树的结果,下面是代码
/**
* Inserts the specified key-value pair into the symbol table, overwriting the old
* value with the new value if the symbol table already contains the specified key.
* Deletes the specified key (and its associated value) from this symbol table
* if the specified value is {@code null}.
*
* @param key the key
* @param val the value
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public void put(Key key, Value val) {
if (key == null) throw new IllegalArgumentException("calls put() with a null key");
if (val == null) {
delete(key);
return;
}
// 更新root
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;
}
第一个put主要是检查用途,最关键的一行是 root = put(root, key, val);
,这是调用的入口
然后我们看下面的put函数,它是一个递归调用,最后会返回一个Node,整个函数的用途相当于:给定一颗 root = x
的树,返回在这棵树插入key
后的新树
我们看函数里面的内容,还是类似二分查找,如果key小于当前节点x
的key,那么应该在x
的左子树插入,插入后需要更新x.left
所以有x.left = put(x.left, key, val);
,大于的情况同理
当我们走到一个空指针的时候,直接返回一个 new Node...
即可,退回到上一层,相当于x.left = new Node(key, ...)
。(以left为例)
注意到x.size = 1 + size(x.left) + size(x.right);
,在插入结束返回上一层时,这一行代码会不断的更新结点的size
属性,最终完成所有经过的结点的更新
deleteMin()
给定一棵以x
为根节点的树,deleteMin()可以删除这棵树中最小的结点,并返回删除后的树
同样根据这个意思root = deleteMin(root);
是调用的入口
/**
* Removes the smallest key and associated value from the symbol table.
*
* @throws NoSuchElementException if the symbol table is empty
*/
public void deleteMin() {
if (isEmpty()) throw new NoSuchElementException("Symbol table underflow");
root = deleteMin(root);
assert check();
}
private Node deleteMin(Node x) {
// 如果当前节点是最小节点,返回这个结点的右子树
// 回到递归上层,相当于把左子树指针指向删除结点(最小节点)的右子树
if (x.left == null) return x.right;
x.left = deleteMin(x.left);
x.size = size(x.left) + size(x.right) + 1;
return x;
}
利用BST的性质,我们一直往左走即可,关键是我们看走到底时候的操作 if (x.left == null) return x.right;
说明如果当前节点没有左孩子,那么我们返回这个节点的右指针
返回了能干嘛呢,看这行代码 x.left = deleteMin(x.left);
,此时的x
是返回之前的x的父节点,我们用father
来描述它,father.left = deleteMin(father.left);
,那么根据返回实际上这行代码意思是 father.left = father.left.right;
,其中father.left = x
注意到我们还有更新size的代码,这个代码和put一样
下面是操作过程的图例
还有镜像的函数deleteMax()
,和上面的操作步骤一样,这里不贴了
min()
找到key最小的结点
/**
* Returns the smallest key in the symbol table.
*
* @return the smallest key in the symbol table
* @throws NoSuchElementException if the symbol table is empty
*/
public Key min() {
if (isEmpty()) throw new NoSuchElementException("calls 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);
}
delete()
重点来了,delete能够删除key为给定值的结点,并且返回删除后的新树
/**
* Removes the specified key and its associated value from this symbol table
* (if the key is in this symbol table).
*
* @param key the key
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public void delete(Key key) {
if (key == null) throw new IllegalArgumentException("calls delete() with a null key");
root = delete(root, key);
assert check();
}
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.right = deleteMin(t.right);
x.left = t.left;
}
x.size = size(x.left) + size(x.right) + 1;
return x;
}
同样root = delete(root, key);
是调用入口
对于下面的delete方法,我们首先需要找到x.key == key
的结点x,寻找过程还是binarySearch
找到之后,如果x结点只有一个孩子,那么我们直接返回这个孩子即可
如果不是,我们做如下操作
- 找到x右子树的最小节点,这个节点实际上是x的后继
- 我们delete掉右子树的最小节点,然后把x的后继节点指向操作后x的右子树
- 更新后继节点的左子树为x的左子树
此时待删除结点已经没有指针指向它,它将被回收,而它的后继节点代替它成为新的节点,并不会破坏树的结构
操作步骤
剩下的一些代码,不涉及BST结构的改变,大部分都是在二分的基础上改进,这里就不贴了