原本是在一张纸上画出了红黑树插入操作的所有情况的演变图示,但前两天将删除操作写到博客上,因此想来,索性就将插入操作也一并写上,以免日后图纸丢失,也方便大伙共同研究,学习。
红黑树的插入操作
1:节点命名约定
N表示新添加的节点。即:取 New 的首字母;
P 表示父节点。即:取 Parent 的首字母;
S表示兄弟姐妹节点。即:取 Sibling的首字母;
G表示祖父节点。即:取 Grandfather的首字母;
Nil表示叶子节点。即所谓的空节点;注意:红黑树中的叶子节点与其他树中所述说的叶子节点不是同一概念。而且红黑树中的叶子节点(即:Nil节点)永远是被定义为黑色的。
2:插入操作宏观分析
对于二叉搜索树的插入操作总是插入在某个叶子节点处(注意:此处的叶子并非指rb-tree的Nil叶子节点,而是真实的叶子节点),而rb-tree的插入也不例外。另外,rb-tree的插入操作我们总是以红色节点插入,如此我们可以保证rb-tree的黑节点数的任何分支上的平衡。当然以红色节点插入,就有可能违背任何父子节点不能同时为红的性质,所以此时需要额外做一些调整处理。
因此,在红黑树中,插入一个节点往大的说,只有以下几种情况:
情况一:红N黑P;(其中N又分为在P的左子树插入以及右子树插入)
情况二:红N红P黑S;(其中N又分为在P的左子树插入以及右子树插入)
情况三:红N红P红S;(其中N又分为在P的左子树插入以及右子树插入)
其中情况一,插入后的rb-tree的任何性质都没有被破坏,因此不需要做任何调整处理。情况二和情况三插入后均违背了任何父子节点不能同时为红的性质,所以下面我们将全面讨论如何通过调整,重新让最终的rb-tree合法化。
3:红黑树插入后平衡处理
在具体分析之前,再次列出红黑树的定义:
1) 任何一个节点非红即黑;
2) 树的根为黑色;
3) 叶子节点为黑色(注意:红黑树的所有叶子节点都指的是Nil节点);
4) 任何两个父子节点不可能同时为红色;
5) 任何节点到其所有分枝叶子的简单路径上的黑节点个数相同;
下面是几个图示说明:
根据前面的分析,插入节点的所有情况罗列如下:
a) 红N黑P,且N为左子节点插入:
分析:该情况则新插入的节点N,在插入后不会破坏任何性质,因此插入结束。此时,我们还可推测P的右子树,要么是一个红色节点要不就是一个Nil节点。
b) 红N黑P,且N为右子节点插入。
分析:该情况则新插入的节点N,在插入后不会破坏任何性质,因此插入结束。此时,我们还可推测P的左子树,要么是一个红色节点要不就是一个Nil节点。
c) 红N红P黑S,且N为左子节点插入。
根据rb-tree的性质,P为红,则P的父节点,即:对N来说就是G节点,必为黑色节点,否则违背性质4。再根据性质5,则我们可以推测出此时的S节点,也必为Nil节点。否则经过G节点的两个分支的黑节点数将不平衡,即原rb-tree就已经不平衡。
分析:插入后,明显的G的左子树深度变为2,右子树却为0(注意:此时S是Nil节点),因此需要以G节点进行一次右旋转。旋转后再将P和G的颜色变换,此时原本经过G的节点的任何路径黑节点数为2,现在经过P节点的任何路线的黑节点数也为2,此时所有性质都满足:
操作:
ð 将G右旋转
ð 将P由黑色改为红色
ð 将G改为红色,此时整棵树已完全满足rb-tree的所有性质。
说明:其实这种情况就是AVL树的LL插入的情况。
d) 红N红P黑S,且N为右子节点插入。
这种情况下,其实我们可以通过一次旋转操作,转变成前面的c)状态。因为对于红P而言,现在新插入的红N位置,在未插入N时,该位置必为Nil节点。因此,P的左子节点也必为Nil节点(不明白的同学,请看性质)。因此,在插入N后,我们可以简简单单地对P做一次左旋转,就转变成了c)情况。只是旋转后的P节点相当于c)中的N,即:此时将P作为新插入的节点,再进行调整即可。
分析:经过上面左旋转后,此时将P节点当作是新插入的节点”N”,再转向c)情况处理即可。
操作:
ð 将P左旋转
ð 以旋转后的P节点作为新插入的节点”N”
ð 转向c)情况处理。
说明:这种情况其实就是AVL中的LR插入操作。
e) 红N红P红S,且N为左子节点插入。
根据rb-tree的性质,红P红S,则G必为黑。因此,原先经过G节点的黑节点数必为2。且原先的P和S的左、右子节点必都为Nil。
分析:旋转前,原本经过G节点的黑节点数为2。旋转后,原本经过G的节点却变成经过P节点。而经过P的右分支的黑节点数为2不变,但经过P的左子树的黑节点数却变为1,减少了1,因此,直接将新插入的节点N由红色改为黑色。如此,不论是原本经过G的还是现在经过P的,黑节点数都是平衡的。但由于原本的G位置是黑色的,现在却变成红色。因此,可以将旋转后的P节点当成是新插入的节点,再转向a)情况处理即可。(即:所谓的上溯)。
操作:
ð 将G右旋转
ð 将N节点由红色改为黑色
ð 以此时的P节点作为新插入的节点”N”,转向a)情况处理即可。
说明:这种情况就是AVL中的LL插入操作。
f) 红N红P红S,且N为右子节点插入。
根据rb-tree的性质,红P红S,则G必为黑。因此,原先经过G节点的黑节点数必为2。且原先的P和S的左、右子节点必都为Nil。并且有了前面的e)点分析,其实不难想到,我们可以将f)情况通过简简单单的一个左旋转操作,将问题变换为e)情况。
分析:将P左旋转后,此时将旋转后的P节点当成是将插入的节点”N”,则情况就跟e)点完全一样。
操作:
ð 将P左旋转;
ð 将P作为新插入的节点”N”;
ð 转向e)情况处理即可。
说明:这种情况就是AVL中的LR插入操作。
至此,红黑树的L型(即:LL和LR)插入全部介绍完毕。对于R型(即:RL和RR)插入的情况,仅仅只是上面b)、c)、d)、e)、f)情况中的旋转方向相反一下即可。思路完全相同。另外,与红黑树的删除操作相比,插入操作显得非常的直观,没有删除操作那么复杂。其实将上述的a)到f)情况绘制在一张纸上的话,一张纸就可以完全说明清楚以上全部情况。(有兴趣的同学可以试下看)。
另外,还有一个小细节需要注意,如果在上溯的过程中,如果已经到了树的根了,即:如上面的e)情况中,则此时直接将节点P改为黑色即可。因为已经到根了,P即为根节点。根据性质2,根节点必为黑色。并且此时将P改为黑色,则rb-tree的任何性质都不会被破坏,插入结束。
Q&A
前面e)情况中,为什么要旋转再变色,而不直接改变G为红色,改变P为黑色,改变S为黑色,然后再将G为新插入的节点转到a)情况处理?
ð 个人的理解:要这么转换也是可以的,但从操作步骤数上,将比上文中e)的操作来的多,因此花的时间也将更多。
以上为个人理解,有错误之处,欢迎指正!