花了差不多一个星期的时间读完了STL红黑树的实现,并凭理解自己写了出来
参考了《算法导论》,教你透彻了解红黑树,强烈推荐,《STL源码剖析》
记录下自己的理解
RB_TREE
红黑树优点:
与BST相比插入,查找,删除在最坏情况下的复杂度仍为O(lgn),相比BST应用范围更广
STL容器set,map都以红黑树为底层容器实现,Linux进程调度算法也用红黑树实现
四种性质:
1. 每个结点要么是红的,要么是黑
2. 根结点是黑的
3. 如果节点为红,子节点必为黑
4. 任一节点至NULL的任何路径,所含黑节点数必定相同
视NULL节点为黑
举个例子,此树为红黑树
1. 旋转
红黑树有两种旋转操作,左旋和右旋,都是为了使树保持平衡
1.1左旋
左旋将支点右子树提上去,取代支点位置,支点右子节点的左子树接到支点右子树上
相当于把左边拉高,右边降低
画图表示如下(懒用VISIO画图,手绘…)
左旋代码:
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rotate_left(link_type x) {
//x为旋转支点,将x右节点左旋,y顶替x
link_type y = x->right;
x->right = y->left;
//设父节点
if (y->left != nullptr)
y->left->parent = x;
y->parent = x->parent;
if (x == root())
root() = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
//父子颠倒
y->left = x;
x->parent = y;
}
2.2 右旋
画图示意
右旋代码:
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rotate_right(link_type x) {
//x为旋转支点,将x左节点右旋,y顶替x
link_type y = x->left;
x->left = y->right;
if (y->right != nullptr)
y->right->parent = x;
y->parent = x->parent;
if (x == root())
root() = y;
else if (x == x->parent->left)
x->parent->left = y;
else
x->parent->right = y;
//父子颠倒
y->right = x;
x->parent = y;
}
2. 插入
与二叉搜索树相同,x比当前节点小往左走,比当前节点大往右走
因为不能破坏性质4,插入节点的颜色肯定要是红色,如果它的父节点是黑色没问题,但是如果它的父节点也是红色,那就破坏了性质3,需要修复节点
插入后导致不平衡的三种情况:
1. 父节点为红,叔叔节点为红
2. 父节点为红,叔叔节点为黑,当前节点为父节点右子
3. 父节点为红,叔叔节点为黑,当前节点为父节点左子
对应解法:
以父节点为左子说明,父节点为右子时类似
- 父节点和叔叔节点涂黑,祖父节点涂红,当前节点移到祖父节点
- 父节点为当前节点,以当前节点为支点左旋(此后导致情况3)
- 父节点涂红,祖父节点涂黑,以祖父节点为支点右旋
画过程图如下:
case1
4为插入的新节点
经过解法1后变为
此时为情况2
经过解法2后变为
此时为情况3
经过解法3后变为
至此达到平衡
插入修复代码:
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rebalance_for_insert(link_type x) {
x->color = rb_tree_red;
while (x != root() && x->parent->color == rb_tree_red) {
//父节点为左子节点
if (x->parent == x->parent->parent->left) {
link_type uncle = x->parent->parent->right;
if (uncle != nullptr && uncle->color == rb_tree_red) {
//case1
x->parent->color = rb_tree_black;
uncle->color = rb_tree_black;
x->parent->parent->color = rb_tree_red;
x = x->parent->parent;
}
else {
//case2
if (x == x->parent->right) {
x = x->parent;
rb_tree_rotate_left(x);
}
//case3
x->parent->color = rb_tree_black;
x->parent->parent->color = rb_tree_red;
rb_tree_rotate_right(x->parent->parent);
}
}
else { //父节点为右子
//case1
link_type uncle = x->parent->parent->left;
if (uncle != nullptr && uncle->color == rb_tree_red) {
uncle->color = rb_tree_black;
x->parent->color = rb_tree_black;
x->parent->parent->color = rb_tree_red;
x = x->parent->parent;
}
else {
//case2
if (x == x->parent->left) {
x = x->parent;
rb_tree_rotate_right(x);
}
//case3
x->parent->color = rb_tree_black;
x->parent->parent->color = rb_tree_red;
rb_tree_rotate(x->parent->parent);
}
}
}
root()->color = rb_tree_black;
}
3. 删除
与二叉搜索树相同,从红黑树中删除一个节点用左子树的最右节点或者右子树的最左节点来替换它,并且保持删除点的颜色不变。当替换点为红色时没有问题,但是当替换点为黑色时,则所有包含之前替换点的路径少了一个黑节点,破坏性质4导致了不平衡
接下来的讨论都是基于用左子树的最右子节点替换,并且替换点的左子节点为初始的当前节点x
若当前节点为红,直接涂黑即可
删除后导致不平衡的四种情况:
1. 当前节点为黑,兄弟节点为红
2. 当前节点为黑,兄弟节点为黑,兄弟节点左右子都为黑
3. 当前节点为黑,兄弟节点为黑,兄弟节点右子为红,左子为黑
4. 当前节点为黑,兄弟节点为黑,兄弟节点左子为红,右子为任意色
解法:
1. 兄弟节点涂黑,父节点涂红,以父节点为支点右旋(此后导致情况2或3或4)
2. 兄弟节点涂红,当前节点父节点为当前节点
3. 兄弟节点涂红,右子涂黑,以兄弟节点为支点左旋
4. 兄弟节点涂为父节点颜色,父节点和兄弟节点左子涂黑,以父节点为支点右旋,结束
画图说明:
解法的所有目的都是为了把当前节点父节点之下的路径的黑节点数调到平衡(举个例
子,若当前节点为右子树,因为之前删除的当前节点的父节点是黑色的,则现在当
前节点的父节点往右所有的路径上黑色节点的个数减少了1,我们通过变换把当前
节点父节点左子树上所有的黑色节点也减少1,则包含当前节点父节点的所有路径
黑节点数少1, 把当前节点移到当前节点父节点,如果为红或者为根,则把它涂黑
结束,如果为黑则继续这些步骤)
如果用右子树的最右子节点替换,我也画了如下变化图
删除修复代码:
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rebalance_for_erase(link_type x, link_type parent_of_x) {
while (x != root() && (x == nullptr || x->color == rb_tree_black)) {
//当前节点为右子树
if (x == parent_of_x->right) {
link_type silbing = parent_of_x->left;
// case1
if (silbing->color == rb_tree_red) {
silbing->color = rb_tree_black;
parent_of_x->color = rb_tree_red;
rb_tree_rotate_right(parent_of_x);
silbing = parent_of_x->left;
}
// case2
if ((silbing->left == nullptr || silbing->left->color == rb_tree_black) &&
(silbing->right == nullptr || silbing->right->color == rb_tree_black)) {
silbing->color = rb_tree_red;
x = parent_of_x;
parent_of_x = parent_of_x->parent;
}
else {
//case3
if (silbing->left == nullptr ||
silbing->left->color == rb_tree_black) {
//if (silbing->right != nullptr)
silbing->right->color = rb_tree_black;
silbing->color = rb_tree_red;
rb_tree_rotate_left(silbing);
silbing = parent_of_x->left;
}
//case4
silbing->color = parent_of_x->color;
parent_of_x->color = rb_tree_black;
silbing->left->color = rb_tree_black;
rb_tree_rotate_right(parent_of_x);
break;
}
}
else { //当前节点为左子树
link_type silbing = parent_of_x->right;
// case1
if (silbing->color == rb_tree_red) {
silbing->color = rb_tree_black;
parent_of_x->color = rb_tree_red;
rb_tree_rotate_left(parent_of_x);
silbing = parent_of_x->right;
}
// case2
if ((silbing->left == nullptr || silbing->left->color == rb_tree_black) &&
(silbing->right == nullptr || silbing->right->color == rb_tree_black)) {
silbing->color = rb_tree_red;
x = parent_of_x;
parent_of_x = parent_of_x->parent;
}
else {
// case3
if (silbing->right == nullptr ||
silbing->right->color == rb_tree_black) {
silbing->color = rb_tree_red;
silbing->left->color = rb_tree_red;
rb_tree_rotate_right(silbing);
silbing = parent_of_x->right;
}
// case4
silbing->color = parent_of_x->color;
parent_of_x->color = rb_tree_black;
silbing->right->color = rb_tree_black;
rb_tree_rotate_left(parent_of_x);
break;
}
}
}
if (x != nullptr)
x->color = rb_tree_black;
}
4. 小结
学透红黑树花了不少时间,深感人类的伟大,实现技巧的巧妙,很多只是看懂了这么做,但是他为什么这么做,他是怎么想出来的就想不透了。世界需要这种具有开创性的人,推动社会的发展,推动人类文明的进步,真是深感佩服!