红黑树本质是一颗二叉查找树,增加了着色以及相关的性质,使得红黑树的查找,插入,删除的时间复杂度最坏为O(log n)。
一、红黑树相对二叉查找树来说,有以下五个性质。
a.红黑树的节点不是红色就是黑色
b.红黑树中根节点必是黑色。
c.红黑树上的节点时红色,它的两个子节点必须是黑色
d.树中任意一个节点到叶子结点的路径上的黑色节点数目相同
f.每个叶子节点都是黑色
二、理解左旋与右旋
当对红黑树进行删除或者是插入工作时,为了保证树的平衡与性质,必须要对红黑树进行调整,调整时可能需要进行旋转操作。通过对节点的着色以及树的旋转操作,来对红黑树进行插入或者是删除节点等操作。当父节点颜色为红色,需要通过旋转或者是颜色改变来维护红黑树的性质。
1.左旋,以下是左旋转的C++源码(来源于STL源码剖析)
inline void _rb_tree_rotate_left(_rb_tree_node_base*x,_rb_tree_node_base*&root)
{
x为旋转点
_rb_tree_node_base*y=x->right;//令y为旋转点的右节点
x->right=y->left;
if(y->left!=0)
{
y->left->parent=x;//设定父节点
}
//令y完全顶替x的地位
y->parent=x->parent;
if(x==root) //如果x为根节点
root=y;
else if(x==x->parent->left)//如果x为其父节点的左节点
x->parent->left=y;
else //如果x为父节点的右节点
x->parent->left=y;
y->left=x;
x->parent=y;
}
2.右旋,以下是右旋转的C++源码(来源于STL源码剖析)
inline void _rb_tree_rotate_left(_rb_tree_node_base*x,_rb_tree_node_base*&root)
{
x为旋转点
_rb_tree_node_base*y=x->left;//令y为旋转点的右节点
x->left=y->right;
if(y->right!=0)
{
y->right->parent=x;//设定父节点
}
//令y完全顶替x的地位
y->parent=x->parent;
if(x==root) //如果x为根节点
root=y;
else if(x==x->parent->lright)//如果x为其父节点的左节点
x->parent->right=y;
else //如果x为父节点的右节点
x->parent->left=y;
y->right=x;
x->parent=y;
}
如果用到双旋转,只是左旋和右旋转的结合应用,这里不一一细描述。树在经过左旋右旋之后,树的搜索性质保持不变,但树的红黑性质则被破坏了,所以,红黑树插入和删除数据后,需要利用旋转与颜色重涂来重新恢复树的红黑性质。
三、红黑树的插入节点
通过红黑树的性质的分析,插入节点会产生四种不同的典型情况,分析以下图RB-Tree分别插入3,8,35,75根据二叉查找树的规则,我们分别得到以下图的插入位置,我们发现它们都破坏了红黑树的规则,我们必须调整树形,也就是旋转树形以及改变节点颜色。(以下灰色为红色,深色为黑色)。
以上1.2.3.4为四种不符合红黑树的四种情况,破外了红黑树的性质,我们必须通过旋转树或者是调整节点颜色。为了方便定义,让我们首先为了这些特殊节点定义一些代名,以下讨论将沿用这些代名。假设新节点为X,其父节点为P,祖父节点为G,伯父节点(父亲之兄弟节点)为S,曾祖父节点为GG,现在根据二叉查找树的规则,新节点必然是叶节点。根据红黑树规则4,X必为红色。若P为红色(这就违反了规则3,必须调整树形)则G为黑色(原为RB-tree,必须遵循规则3)。于是,根据X的插入位置以及外围的节点(S和GG)的颜色,有四种以下典型情况。
1.S为黑且X为外侧插入。对此情况,我们队P,G做了一次但旋转,并更改P,G颜色,即可重新满足红黑树的规则3。见下图
注意,此时可能产生不平衡状态(高度相差为1以上)。例如图中A和B为null,D或者E不为NULL。这倒是没关系,因为RB-Tree的平衡性本来就比AVL-tree弱。然而RB-tree通常能够导致良好的平衡状态。是的,经验告诉我们,RB-tree的搜索平均效率和AVL-tree几乎相等。
2.S为黑且X为内侧插入。对此情况,我们必须先对P,X做一次单旋转并更改G,X颜色,再将结果对G做一次单旋转,即可在此满足红黑树规则3.
3.S为红色X为外侧插入。对此情况。我们先对P和G做一次单旋转,并改变X的颜色。此时如果GG为黑,一切搞定,但如果GG为红色,则问题如4状况。
4.S为红色X为外侧插入,对此情况,先对P和G做一次单旋转,并改变X的颜色。此时如果GG亦为红色,还要持续往上做,直到不再有父子连续并为红色的情况。
四、红黑树的删除
红黑树是一种特殊的二叉查找树,其删除结点首先要按二叉查找树删除结点的算法进行
(一).普通二叉查找树删除一个结点:
(1)待删除结点没有子结点,即它是一个叶子结点,此时直接删除
(2)待删除结点只有一个子结点,则可以直接删除;如果待删除结点是根结点,则它的子结点变为根结点;如果待删除结点不是根结点,则用它的子结点替代它的位置。
(3)待删除结点有两个子结点,首先找出该结点的后继结点(即右子树中数值最小的那个结点),然后将两个结点进行值交换(即:只交换两个结点的数值,不改变结点的颜色)并将待删除结点删除,由于后继结点不可能有左子结点,对调后的待删除结点也不会有左子结点,因此要把它删除的操作会落入情况(1)或情况(2)中。
(二)红黑树的删除结点算法
1.待删除结点有两个外部结点,操作如下:
(1)直接把该结点调整为叶结点
(2)若该结点是红色,则可直接删除,不影响红黑树的性质,算法结束
(3)若该结点是黑色,则删除后红黑树不平衡。此时要进行“双黑”操作
记该结点为V,则删除了V后,从根结点到V的所有子孙叶结点的路径将会比树中其他的从根结点到叶结点的路径拥有更少的黑色结点,破坏了红黑树性质4。此时,用“双黑”结点来表示从根结点到这个“双黑”结点的所有子孙叶结点的路径上都缺少一个黑色结点。
双黑含义:该结点需要代表两个黑色结点,才能维持树的平衡
2.待删除结点有一个外部结点
操作为:该节点是黑色,其非空子节点为红色 ;则将其子节点提升到该结点位置,颜色变黑
3.“双黑”结点的处理
分三种情况:
(1)双黑色结点的兄弟结点是黑色,且子结点有红色结点
(2)双黑结点的兄弟结点是黑色,且有两个黑色结点
(3)双黑结点的兄弟结点是红色结点
(1)双黑结点的兄弟结点是黑色,且子结点有红色结点
A种情况:双黑结点远侄子结点(双黑结点若为左孩子,则双黑结点的兄弟结点的右孩子为远侄子结点;同理,处理双黑结点为右孩子)为红色,如图2
处理方法:把兄弟结点染为双黑结点的父亲结点的颜色,把兄弟结点的右孩子染为黑色,再把父结点染为黑色;然后针对父结点进行一次左旋转,如图3 。
B种情况:双黑结点近侄子结点(双黑结点若为左孩子,则双黑结点的兄弟结点的左孩子为近侄子结点;同理,处理双黑结点为右孩子)为红,如图4
处理方法:针对双黑结点的兄弟做一次右旋转,结果使双黑结点的近侄子成为双黑结点新的兄弟;将新兄弟结点着为双黑结点的父结点的颜色,父结点着为黑色,再针对父做一次左旋转,如图5
(2)双黑结点的兄弟结点是黑色,且有两个黑色结点,如图6
处理方法:把双黑结点的兄弟结点着为红色,双黑结点的父结点着为黑色;若父结点原来为红色,则算法结束;若父结点原来黑色,则将父结点作为双黑结点,继续调整,如图7
(3)双黑结点的兄弟结点是红色结点,如图8
处理方法:单旋转为情况1或情况2,并改变双黑结点的兄弟结点的颜色及父结点的颜色(如图9)
文章参考了侯捷的《STL源码剖析》以及参考了 http://gengning938.blog.163.com/blog/static/1282253812011420103852696/