红黑树的插入的时间复杂度是O(lgn),插入操作和普通的二叉搜索树几乎一模一样,不同点是在插入后需要看看红黑树的五大性质是否被破坏了,如果被破坏了,那就需要调整了。下面先来看看红黑树插入操作的代码,其实插入操作很简单,假如要插入的节点叫做x,无非就是找到从根节点出发,发现当前节点比x大的走左边路线,发现当前节点比x小的走右边路线,一直走到叶子节点,然后插入这个x节点。关键点是插入后的调整。下面先看看红黑树的插入伪代码(抄自算法导论)
RB-INSERT(T, x)
y = T.NIL;
z = T.root;
// 一直循环直到找到z的合适位置
while z != T.NIL
y = z;
if x.key < z.key
z = z.left;
else z = z.right;
x.p = y;
if y == T.NIL
T.root = x;
elseif x.key < y.key
y.left = x;
else y.right = x;
x.left = T.NIL;
x.right = T.NIL;
x.color = RED;
// 调整恢复红黑树的五大性质
RB-INSERT-FIXUP(T, x);
注意到上面我们插入的节点染为红色,为什么不是黑色呢?因为如果黑色的话,调整起来会复杂一些,具体我们后面看删除操作就明白了。下面我们分析一下插入红色节点x后整棵红黑树的变化。首先性质1、3、5肯定是没有变化的。仅可能破坏性质2(x为根节点)和性质4(父节点是红色的)。性质2破坏很好解决,直接染黑就是了。我们重点来讨论性质4被破坏后,如何调整和恢复。这里先说一个指导思想,1.调整恢复过程中千万不能破坏其他性质,否则只会带来更多难以解决的问题,2.指针指向的是需要调整的节点,只要我们能把指针指向的节点的父节点调整为黑色,并且没有破坏性质5,那么问题就解决了。那么总结一下,插入后的所有情况值可能有如下五种
case 0.插入节点z为根节点——这种情况好办,直接染黑完事,我们不讨论
case 1.父节点为黑色节点——这种情况最好办,根本没有破坏红黑树的五大性质,不需要做任何调整,我们不讨论
case 2.父节点为红色节点,叔节点也为红色
case 3.父节点为红色节点,叔节点为黑色节点,当前节点为右孩子
case 4.父节点为红色节点,叔节点为黑色节点,当前节点为左孩子
就以上五种情况已经列举了所有可能,我们不关心兄弟节点的颜色,为什么呢?这里可留给大家思考一下,稍微想下就明白了。下面用一个图展示所有需要讨论的情况:
下面我们一个情况一个情况地来分析。
先看case 2:
可以看到 父节点和叔节点都是红色,再想想我们的指导思想2,要把父节点染黑,那就染黑节点p,这样子的话z子树的黑高就加1了,那只有把节点a染红,同时不能让w子树黑高减一,那就把w染黑,这样z的父节点就成功染黑了,修复了性质4,而且没有破坏性质5。那么上图的红黑树的子树性质就恢复了。此时指针指向节点a,再看看a往上的子树的五大性质是否有保持,此时情况可能转case1,2,3,4,如果转了case1,那么性质就恢复了。看看伪代码,伪代码如下:
// case 2伪代码
// 叔节点
y == x.p.p.right;
if y.color == red
x.p.color = black;
y.color = black;
x.p.p.color = red;
x = x.p.p;
接着来看case 3:
这个case 3比较有趣,父节点为红色,叔节点为黑色,当前节点为右孩子。一看两个红节点,怎么旋转都不会破坏红黑树原有的性质,而且旋转一下,把当前节点指向节点p的话,情况就变得和case 4一模一样了。变为了父节点(z)红色,叔节点为黑色,当前节点为左孩子。这样正好我们把case 3和case 4合并起来了,只要解决case 4即可。看看case 3伪代码,只需两行:
// case 3
x = x.p;
LEFT-ROTATE(T, x)
那么接下来看看case 4
case 4中我们一步步的分析如何从左边的图调整为右边的图,首先还是回到我们的指导思想,把x指针指向节点的父节点染黑,染黑后发现改变了子树Q和W的黑高,那么一个做法就是右旋转a节点,右旋节点a后发现子树F和G的黑高加了1,破坏了性质5,那么把节点a染红,正好就把黑高调整回来了,经过这样的调整,也就变成了上面右边的红黑树图案了。至此,性质4恢复了,红黑树的插入调整也正常结束。case 4伪代码:
// case 4
x.p.color = black;
x.p.p.color = red;
RIGHT-ROTATE(T, x.p.p);
至此红黑树的插入操作分析完毕。下面整理一段整个插入流程的伪代码,以方便大家全局观看插入操作:
// 红黑树插入逻辑
RB-INSERT-FIXUP(T, x)
while x.p.color = red
if x.p == x.p.p.left
y == x.p.p.right;
if y.color == red
// case 2
x.p.color = black;
y.color = black;
x.p.p.color = red;
x = x.p.p;
else if x == x.p.right
// case 3
x = x.p;
LEFT-ROTATE(T, x)
// case 4
x.p.color = black;
x.p.p.color = red;
RIGHT-ROTATE(T, x.p.p);
else(x.p为右子树,和左子树操作相反即可);
T.root.color = black;