二叉查找树在最坏的情况下性能很差时间复杂度是O(N),本节我们学习一种新的数据结构红黑树
它能保证操作在最坏情况下时间复杂度也是O(logN),无论怎么构造,它都能到达平衡,红黑树结构很复杂,首先我们学习一种叫2-3树的数据结构来帮忙对红黑树的理解。
一般的二叉查找树一个节点只能有一个键两个子节点,在2-3树中一个节点最多可以有两个键3个子节点,左子树中的键都比左边的键小,
中间子树的键大于左边的键小于右边的键,右子树的键都大于右边的键,2-3树的示意图如下
//我们结合下图来理解2-3树的构造过程,
插入的过程总是先把一个节点插入满3个键再进行分离,看右图的过程,先插入A,C,然后插入E,此时节点含3个键进行分裂,A,E各占一个节点,C移到父节点,然后插入H,L后再进行分裂,H移到父节点,然后L,M,P进行分裂,M移到父节点,这时父节点包含CHM3个键,所以父节点继续往上分裂,分裂的过程不会影响数的有序性和平衡性
从图中可以看出不论怎么构造2-3树总能达到平衡
直接用2-3树这种数据结构来达到平衡行也可以实现,但是不太方便,因为要考虑的情况太多
红黑树对2-3树的结构做了些改变,使用简单的结构就可以表达和实现2-3树
红黑树把2-3树中含有两个键的节点想象成2个用红线连接在一起的节点,2-3树中的普通链接用黑线表示,这样红黑树还是一棵二叉树,红黑树和2-3树的对应关系如下
红黑树有以下特征:
1.红链接均为左链接
2.没有节点同时和2个红链接相连,相当于含3个键的2-3树
3.红黑树是完美黑色平衡的,即每个叶子节点到根节点黑链接数量相同
//红黑树的实现过程如下:
public class RedBlackBST<Key extends Comparable<Key>, Value> {
private static final boolean RED = true;
private static final boolean BLACK = false;
private Node root; // root of the BST
// BST helper node data type
private class Node {
private Key key; // key
private Value val; // associated data
private Node left, right; // links to left and right subtrees
private boolean color; // color of parent link,每个节点增加一个颜色值,代表父节点指向自己链接的颜色
private int size; // subtree count
public Node(Key key, Value val, boolean color, int size) {
this.key = key;
this.val = val;
this.color = color;
this.size = size;
}
}
/**
* Initializes an empty symbol table.
*/
public RedBlackBST() {
}
/***************************************************************************
* Node helper methods.
***************************************************************************/
// is node x red; false if x is null ?
private boolean isRed(Node x) {
if (x == null) return false;
return x.color == RED;
}
// number of node in subtree rooted at x; 0 if x is null
private int size(Node x) {
if (x == null) return 0;
return x.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);
}
/**
* Is this symbol table empty?
* @return {@code true} if this symbol table is empty and {@code false} otherwise
*/
public boolean isEmpty() {
return root == null;
}
/**
* Does this symbol table contain the given key?
* @param key the key
* @return {@code true} if this symbol table contains {@code key} and
* {@code false} otherwise
* @throws IllegalArgumentException if {@code key} is {@code null}
*/
public boolean contains(Key key) {
return get(key) != null;
}
/***************************************************************************
* Standard BST search.
***************************************************************************/
/**
* 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) {
if (key == null) throw new IllegalArgumentException("argument to get() is null");
return get(root, key);
}
// value associated with the given key in subtree rooted at x; null if no such key
private Value get(Node x, Key key) {
while (x != null) {
int cmp = key.compareTo(x.key);
if (cmp < 0) x = x.left;
else if(cmp > 0) x = x.right;
else return x.val;
}
return null;
}
/***************************************************************************
* Red-black tree insertion.
***************************************************************************/
/**
* 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("first argument to put() is null");
if (val == null) {
delete(key);
return;
}
root = put(root, key, val);
root.color = BLACK;
// assert check();
}
// insert the key-value pair in the subtree rooted at h
//2-3树在插入过程中总是先往一个节点中插,当节点中键的个数为3时再分裂成2个含有1个键的节点,另一个键移到父节点中,
//移动后如果父节点变成含3个键的节点,则再分裂父节点
//红黑树也是一样,插入的节点总是先标记为红色,然后如果不满足红黑树的特征再进行分裂转换
private Node put(Node h, Key key, Value val) {
if