参考算法导论第三版第十二章: 二叉搜索树。实现代码:百度网盘,提取密码:n6yd。
当普通的二叉搜索树高低较低时,对于树的操作效率较高,考虑这样一种情况:按照关键字从小到大(或从大到小)构建一颗搜索二叉树,这样的一颗树实际上就是一个线性链表,搜索性能很差,为O(n)。与理想的lg(n)有较大差距。当然这是一种最坏情况。而红黑树就是一颗加强版的二叉搜索树,它是一个近似平衡的(从根结点到叶结点的路径中,没有一条路径会比最短路径长出二倍),而当对树进行插入、删除操作后会破坏树的平衡性,要进行一系列操作维护这种平衡性,以达到最坏情况下时间复杂度仍然是lg(n)。
红黑树定义:
红黑树中的结点中维护一个颜色域,表明当前结点是红色结点还是黑色结点。这也是红黑树名字的又来。树中每个结点包含五个属性:color、key-value、left、right和p。如果一个结点没有子结点或父结点,则相应的指针属性为NIL(视NIL为叶结点)。算法导论中对红黑树做如下定义:
1.每个结点或是红色的,或是黑色的。
2.根结点是黑色的。
3.每个叶结点(NIL)是黑色的。
4.如果一个结点是红色的,则它的两个子结点都是黑色的。
5.对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。(黑高)
旋转:
当对一颗红黑树执行插入、修改操作后,红黑树的性质就有可能被破坏。为了维护红黑树的性质,就必须要改变某些结点的颜色和指针(left、right、p)结构。指针结构能够通过【旋转】操作来完成,包括左旋和右旋。对结点x执行左旋,相当于用其右子结点y代替其位置,同时令结点y的左子结点β作为结点x的右子结点,结点x作为结点y的左子结点。右旋与左旋对称。
/**
* 左旋操作。右旋与左旋对称,只要把right和left调换
* @param p 对p结点进行左旋操作
*/
private void leftRotate(TreeNode<K, V> p) { // p对应x,r对应y
if (p != null) {
TreeNode<K, V> r = p.right;
p.right = r.left;
if (r.left != NIL)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == NIL)
root = r;
else if (p == p.parent.left)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
插入:
/**
* 与搜索二叉树插入情况基本类似,每个新插入节点为红色,插入后
* 可能会导致红黑树特性被破坏,需要进行颜色转换和旋转操作修复
* @param key 插入的关键字
* @param value 对应的值
* @param putIfAbsent 设置为true时,只有当前树中不存在关键字key时,执行插入
*/
@SuppressWarnings("unchecked")
private void insert(K key, V value, boolean putIfAbsent) {
TreeNode<K, V> p = root, r = nil;
int cmp;
while (p != nil) {
r = p;
cmp = key.compareTo(p.key);
if (cmp == 0) {
if (putIfAbsent || p.value == null)
p.value = value;
return;
} else if (cmp < 0)
p = p.left;
else
p = p.right;
}
TreeNode<K, V> t = newTreeNode(key, value, r);
if (r == nil)
root = t;
else if (key.compareTo(r.key) < 0)
r.left = t;
else
r.right = t;
size++;
insertFixUp(t); // 插入节点后,进行修复操作
}
插入后修复:
private void insertFixUp(TreeNode<K, V> t) {
while (t.parent.color == RED) {
TreeNode<K, V> uncle;
if (t.parent == t.parent.parent.left) { // 父结点为左子结点
uncle = t.parent.parent.right;
if (uncle.color == RED) { // 情况一,父结点和叔父结点均为红色
// 将父父结点和叔父结点均换成黑色,然后修改祖父结点为红色
t.parent.color = BLACK;
uncle.color = BLACK;
t.parent.parent.color = RED;
// 此时祖父结点有可能不符合红黑树定义,对其进行修正
t = t.parent.parent;
} else if (t == t.parent.right) { // 情况二,叔父结点为黑色,结点t为右子结点
t = t.parent; // 对父结点进行左旋操作,变成情况三
leftRotate(t);
t.parent.color = BLACK; // 情况三,将父结点修改成黑色,祖父结点修改成红色,然后右旋祖父结点
t.parent.parent.color = RED;
rightRotate(t.parent.parent);
}
} else { // 父结点为右子结点,与父结点为左子结点对称(left和right调换)
uncle = t.parent.parent.left;
if (uncle.color == RED) {
t.parent.color = BLACK;
uncle.color = BLACK;
t.parent.parent.color = RED;
t = t.parent.parent;
} else if (t == t.parent.left) {
t = t.parent;
rightRotate(t);
t.parent.color = BLACK;
t.parent.parent.color = RED;
leftRotate(t.parent.parent);
}
}
}
root.color = BLACK;
}