红黑树的插入操作可以在O(logn)的时间内完成。开始插入节点的时候和二叉查找树一样,只需要最后将插入的节点着成红色,为了保证红黑树的性质,需要通过RB_InsertFixUp函数来调整该节点,对其重新着色并旋转。
下面先调用RB_Insert()函数将一个节点插入到红黑树中,同样先上伪代码
RB-INSERT(T, z)
1 y ← nil[T]
2 x ← root[T]
3 while x ≠ nil[T]
4 do y ← x
5 if key[z] < key[x]
6 then x ← left[x]
7 else x ← right[x]
8 p[z] ← y
9 if y = nil[T]
10 then root[T] ← z
11 else if key[z] < key[y]
12 then left[y] ← z
13 else right[y] ← z
14 left[z] ← nil[T]
15 right[z] ← nil[T]
16 color[z] ← RED
17 RB-INSERT-FIXUP(T, z)
接着上C++代码并进行代码分析
bool RB_Insert(int key, int data)
{
RB_Node *p = root;
RB_Node *insert_p = NIL;
//找到插入在哪个节点之后
while(p != NIL)
{
insert_p = p;
if(key < p->key) p = p->left;
else if(key > p->key) p = p->right;
else return false;//重复的数据不进行插入
}
//初始化插入节点,注意插入节点初始的颜色为RED
RB_Node *insert_node = new RB_Node();
insert_node->key = key;
insert_node->data = data;
insert_node->RB_COLOR = RED;
insert_node->right = NIL;
insert_node->left = NIL;
//如果插入的是一颗空树,设定root就是插入节点
if(insert_p == NIL)
{
root = insert_node;
root->parent = NIL;
NIL->left = NIL->right = NIL->parent = root;
}
else//判断插入左节点还是右节点
{
if(key < insert_p->key)
insert_p->left = insert_node;
else insert_p->right = insert_node;
insert_node->parent = insert_p;
}
//修复红黑树的性质
RB_InsertFixUp(insert_node);
return true;
}
为了保证红黑树的结构性质,需要调用RB_InsertFixUp函数来调整刚刚插入的节点
我们考虑下,如果一个红色节点(下文称用Z指向它)被插入到树中,那么有哪些红黑性质可能被破坏呢?只有性质2(根节点是黑色的,由于插入的可能是根节点)以及性质4(红色节点的子节点一定是黑色节点,其父节点是红色的),其它都不会被破坏。
如果插入的节点的父节点是黑色的,那么不需要做任何调整,这红黑树是正常的。如果父节点是红色,或插在树根的位置,那么就要进行调整以保证红黑树性质。
首先对性质4进行分析,我们知道,插入一个新的节点,这个节点肯定会被放到树的底部成为一个叶节点,那么这个红色节点就没有可能和自己的子节点同色(因为叶节点的子节点是NIL节点,都是黑色的),如果性质4被破坏的话,肯定是Z指向的节点的父节点是红色的。因此,为了使分析和解决更加容易和清晰,我们在对树进行调整以恢复红黑特性时,始终使得Z总是指向相邻红色的节点中的子节点(指针Z可能会向上升到树的中部或根部)。基于这个做法,我们可以知道,如果是性质2被破坏了的话,也就是Z指向根节点了,那么性质4肯定就符合了(因为Z的父节点是NIL,黑色的),因此对性质2的恢复变得很简单,只需要被根从红色变为黑色即可。
现在只需要处理性质4被破坏的问题,如果性质4被破坏了,也就是说,Z的父节点是红色的,那么,说明,Z一定有祖父节点,而且是黑色的(否则插入前原树就有问题,又或是调整时的方法不正确)。因此可以把问题放到以Z的祖父节点为根节点的子树内进行解决,这样可以把调整的范围最小化,而且这也是有可能的:只要不改变这子树的黑高度,那么就不会对树的其它部分产生影响。我们要做的就是在这个子树范围内把红黑性质调整回来。再看子树的根是否与其父节点同为红色,是的话,就再次用前面所说的去解决它,一直向上递归到红黑性质被恢复为止。
对性质4的恢复,根据Z的父节点是Z的祖节点的左子节点还是右子节点,分为两组对称的情况,每组有3种情况。下面我们以Z的父节点是Z祖节点的左子节点为例:
定义叔叔的含义:即祖父节点的另外一个子节点
情况1:z的叔叔y是红色的
如上图所示,在这种情况下,将父、叔节点都着为黑色,再将子树根节点着为红色,那么子树的黑高度没有发生改变,而且红黑性质得得到了调整。此时,再将Z指向子树的根节点,向上递归恢复红黑特性。
情况2:z的叔叔y节点是黑色的,而且z是父节点的左子节点
如上图,将Z的父节点与祖节点进行一次右旋,并把父节点着黑色,原来的祖节点着红色。这些子树的红黑特性得到了恢复,而且子树的黑高度没有变化。另外,由于子树根节点已经是黑色了(这个节点不会出现父子同为红色的问题了),所以不必再向上递归了,此时整个树的红黑特性都已经是正确的了。
情况3:z的叔叔y节点是黑色的,而且z是父节点的右子节点
如上图,将Z本身与其父节点进行一次左旋,让Z指向原来的父节点,就可以调整为情况2,而情况2已经得到解决。
由此红黑树插入后调整问题已经解决。
伪代码:
1 while color[p[z]] = RED
2 do if p[z] = left[p[p[z]]]
3 then y ← right[p[p[z]]]
4 if color[y] = RED
5 then color[p[z]] ← BLACK // Case 1
6 color[y] ← BLACK // Case 1
7 color[p[p[z]]] ← RED // Case 1
8 z ← p[p[z]] // Case 1
9 else if z = right[p[z]]
10 then z ← p[z] // Case 2
11 LEFT-ROTATE(T, z) // Case 2
12 color[p[z]] ← BLACK // Case 3
13 color[p[p[z]]] ← RED // Case 3
14 RIGHT-ROTATE(T, p[p[z]]) // Case 3
15 else (same as then clause
with "right" and "left" exchanged)
16 color[root[T]] ← BLACK
RB_InsertFixUp C++源码:
void RB_InsertFixUp(RB_Node *node)
{
while(node->parent->RB_COLOR == RED)
{
if(node->parent == node->parent->parent->left)
{
RB_Node *uncle = node->parent->parent->right;
//情况1,z的叔叔y是红色的
if(uncle->RB_COLOR == RED)
{
node->parent->RB_COLOR = BLACK;
uncle->RB_COLOR = BLACK;
node->parent->parent->RB_COLOR = RED;
node = node->parent->parent;
}
else if(uncle->RB_COLOR == BLACK)
{
//情况3,z的叔叔y是黑色的,z是右孩子
if(node == node->parent->right)
{
node = node->parent;
RB_LeftRotate(node);
}
//情况2,z的叔叔y是黑色的,但z是左孩子
node->parent->RB_COLOR = BLACK;
node->parent->parent->RB_COLOR = RED;
RB_RightRotate(node->parent->parent);
}
}
else
{
RB_Node *uncle = node->parent->parent->left;
if(uncle->RB_COLOR == RED)
{
node->parent->RB_COLOR = BLACK;
uncle->RB_COLOR = BLACK;
uncle->parent->RB_COLOR = RED;
node = node->parent->parent;
}
else if (uncle->RB_COLOR == BLACK)
{
if(node == node->parent->left)
{
node = node->parent;
RB_RightRotate(node);
}
node->parent->RB_COLOR = BLACK;
node->parent->parent->RB_COLOR = RED;
RB_LeftRotate(node->parent->parent);
}
}
}
root->RB_COLOR = BLACK;
}