参考:
《算法导论》
红黑树是一棵二叉搜索树,每个节点有一个标志位表示颜色,该颜色可以是红(RED)或黑(BLACK)。通过对任何一条从根到叶子的简单路径上各点的颜色进行约束,就能确保没有一条路径会比其他路径长出2倍,因而是近似于平衡的。
红黑树每个节点有5个属性,color,key,leftChild,rightChild,p。其中color是颜色,key是关键字,leftChild是指向左孩子的指针,rightChild是指向右孩子的指针,p是指向父节点的指针。如果一个节点没有子节点或父节点,则该节点的leftChild,rightChild,p指针指向NIL。这些NIL视为二叉搜索树的外部节点,其他节点为内部节点。
一棵红黑树有如下性质:
(1)每个节点或是红色,或是黑色;
(2)根节点是黑色的;
(3)每个叶节点(NIL节点)是黑色的;
(4)如果一个节点是红色的,则它的两个子节点都是黑色的;
(5)对每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
如图(a)为一棵红黑树;
我们可以把NIL看成哨兵T.nil,它的color是黑色,其他属性任意。如图(b);
左旋
指针结构的修改是通过旋转来完成的,这时一种能保持二叉搜索树性质的操作。旋转有左旋和右旋两个,左旋操作为:
如图,对x作左旋操作。假设x的右孩子为y(不是NIL),x可以是任何右孩子不为NIL的内部节点,简单理解为:
以x为中心向左旋转,交换x,y;α,β,γ顺序不变。其中还要注意,每个节点还有指向父节点指针。
动图:
左旋的伪代码:
LEFT-ROTATE(T, x)
01 y ← right[x] // 前提:这里假设x的右孩子为y。下面开始正式操作
02 right[x] ← left[y] // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
03 p[left[y]] ← x // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
04 p[y] ← p[x] // 将 “x的父亲” 设为 “y的父亲”
05 if p[x] = nil[T]
06 then root[T] ← y // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
07 else if x = left[p[x]]
08 then left[p[x]] ← y // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
09 else right[p[x]] ← y // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
10 left[y] ← x // 将 “x” 设为 “y的左孩子”
11 p[x] ← y // 将 “x的父节点” 设为 “y”
c++代码:
void Left_Rotate(BiTreeNode* root,BiTreeNode* x)
{
BiTreeNode* y = x->rightChild;
x->rightChild = y->leftChild;
y->leftChild->p = x;
y->p = x->p;
if (x->p == NULL)
root = y;
else if (x->p->leftChild == x)
x->p->leftChild = y;
else
x->p->rightChild = y;
y->leftChild= x;
x->p = y;
}
如上图,对节点“11”左旋;要注意的是:如果x是根节点,旋转后y称为根节点;还有一点,需要知道x是其父节点的左孩子还是右孩子。
右旋
右旋和左旋基本相同:
伪代码:
RIGHT-ROTATE(T, y)
01 x ← left[y] // 前提:这里假设y的左孩子为x。下面开始正式操作
02 left[y] ← right[x] // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
03 p[right[x]] ← y // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
04 p[x] ← p[y] // 将 “y的父亲” 设为 “x的父亲”
05 if p[y] = nil[T]
06 then root[T] ← x // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
07 else if y = right[p[y]]
08 then right[p[y]] ← x // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
09 else left[p[y]] ← x // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
10 right[x] ← y // 将 “y” 设为 “x的右孩子”
11 p[y] ← x // 将 “y的父节点” 设为 “x”
c++代码:
void Right_Rotate(BiTreeNode* root, BiTreeNode* y)
{
BiTreeNode* x = y->leftChild;
y->leftChild = x->rightChild;
x->rightChild->p = y;
x->p = y->p;
if (y->p == NULL)
root = x;
else if (y->p->rightChild == y)
y->p->rightChild = x;
else
y->p->leftChild = x;
x->rightChild = y;
y->p = x;
}
插入
我们先当红黑树为普通的二叉搜索树一样插入节点z,并将其颜色置为RED,然后,为了保证红黑树性质,调用一个辅助函数来调整红黑树。 伪代码:
RB-INSERT(T, z)
01 y ← nil[T] // 新建节点“y”,将y设为空节点。
02 x ← root[T] // 设“红黑树T”的根节点为“x”
03 while x ≠ nil[T] // 找出要插入的节点“z”在二叉树T中的位置“y”
04 do y ← x
05 if key[z] < key[x]
06 then x ← left[x]
07 else x ← right[x]
08 p[z] ← y // 设置 “z的父亲” 为 “y”
09 if y = nil[T]
10 then root[T] ← z // 情况1:若y是空节点,则将z设为根
11 else if key[z] < key[y]
12 then left[y] ← z // 情况2:若“z所包含的值” < “y所包含的值”,则将z设为“y的左孩子”
13 else right[y] ← z // 情况3:(“z所包含的值” >= “y所包含的值”)将z设为“y的右孩子”
14 left[z] ← nil[T] // z的左孩子设为空
15 right[z] ← nil[T] // z的右孩子设为空。至此,已经完成将“节点z插入到二叉树”中了。
16 color[z] ← RED // 将z着色为“红色”
17 RB-INSERT-FIXUP(T, z) // 通过RB-INSERT-FIXUP对红黑树的节点进行颜色修改以及旋转,让树T仍然是一颗红黑树
主要是插入节点后红黑树性质的调整函数
伪代码:
RB - INSERT - FIXUP(T, z)
01 while color[p[z]] = RED // 若“当前节点(z)的父节点是红色”,则进行以下处理。
02 if p[z] = left[p[p[z]]] // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
03 y ← right[p[p[z]]] // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
04 if color[y] = RED // Case 1条件:叔叔是红色
05 color[p[z]] ← BLACK ? Case 1 // (01) 将“父节点”设为黑色。
06 color[y] ← BLACK ? Case 1 // (02) 将“叔叔节点”设为黑色。
07 color[p[p[z]]] ← RED ? Case 1 // (03) 将“祖父节点”设为“红色”。
08 z ← p[p[z]] ? Case 1 // (04) 将“祖父节点”设为“当前节点”(红色节点)
09 else if z = right[p[z]] // Case 2条件:叔叔是黑色,且当前节点是右孩子
10 z ← p[z] ? Case 2 // (01) 将“父节点”作为“新的当前节点”。
11 LEFT - ROTATE(T, z) ? Case 2 // (02) 以“新的当前节点”为支点进行左旋。
12 color[p[z]] ← BLACK ? Case 3 // Case 3条件:叔叔是黑色,且当前节点是左孩子。(01) 将“父节点”设为“黑色”。
13 color[p[p[z]]] ← RED ? Case 3 // (02) 将“祖父节点”设为“红色”。
14 RIGHT - ROTATE(T, p[p[z]]) ? Case 3 // (03) 以“祖父节点”为支点进行右旋。
15 else (same as then clause with "right" and "left" exchanged) // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
16 color[root[T]] ← BLACK
void RB_Insert_FixUp(BiTreeNode* root, BiTreeNode* z)
{
BiTreeNode* y;
while (z->p && z->p->Color == RED)
{
if (z->p == z->p->p->leftChild)
{
y = z->p->p->rightChild;
if (y->Color == RED)
{
z->p->Color = BLACK;
y->Color = BLACK;
z->p->p->Color = RED;
z = z->p->p;
}
else
{
if (z == z->p->rightChild)
{
z = z->p;
Left_Rotate(root, z);
}
z->p->Color = BLACK;
z->p->p->Color = RED;
Right_Rotate(root, z->p->p);
}
}
else
{
y = z->p->p->leftChild;
if (y->Color == RED)
{
z->p->Color = BLACK;
y->Color = BLACK;
z->p->p->Color = RED;
z = z->p->p;
}
else
{
if (z == z->p->leftChild)
{
z = z->p;
Right_Rotate(root, z);
}
z->p->Color = BLACK;
z->p->p->Color = RED;
Left_Rotate(root, z->p->p);
}
}
}
}
删除
将红黑树T内的节点z删除。需要执行的操作依次是:首先,将T当作一颗二叉树,将节点删除;然后,通过RB – DELETE – FIXUP来对节点重新着色并旋转,以此来保证删除节点后的树仍然是一颗红黑树。
(01) 将T当作一颗二叉树,将节点删除。
这和”删除常规二叉搜索树中删除节点的方法是一样的”。分3种情况:
第一种,被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
第二种,被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
第三种,被删除节点有两个儿子。那么,首先把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。
这里有两点需要说明:第一步中复制时,仅仅复制内容,即将“它的后继节点的内容”复制给“该节点的内容”。 这相当于用“该节点的后继节点”取代“该节点”,之后就删除“该节点的后继节点”即可,而不需要删除“该节点”(因为“该节点”已经被“它的后继节点”所取代)。
第二步中删除“该节点的后继节点”时,需要注意:“该节点的后继节点”不可能是双子非空,这个根据二叉树的特性可知。 既然“该节点的后继节点”不可能双子都非空,就意味着“该节点的后继节点”要么没有儿子,要么只有一个儿子。若没有儿子,则按“第一种”种的办法进行处理;若只有一个儿子,则按“第二种”中的办法进行处理。
(02) 通过RB – DELETE – FIXUP来对节点重新着色并旋转,以此来保证删除节点后的树仍然是一颗红黑树。
因为(01)中删除节点之后,可能会违背红黑树的特性。所以需要,通过RB – DELETE – FIXUP来重新校正,为当前树保持红黑树的特性。
下面是《算法导论》中 “从红黑树T中删除节点z”的伪代码
RB - DELETE(T, z)
01 if left[z] = nil[T] or right[z] = nil[T]
02 then y ← z // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
03 else y ← TREE - SUCCESSOR(z) // 否则,将“z的后继节点”赋值给 “y”。
04 if left[y] ≠ nil[T]
05 then x ← left[y] // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
06 else x ← right[y] // 否则,“y的右孩子” 赋值给 “x”。
07 p[x] ← p[y] // 将“y的父节点” 设置为 “x的父节点”
08 if p[y] = nil[T]
09 then root[T] ← x // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。
10 else if y = left[p[y]]
11 then left[p[y]] ← x // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”
12 else right[p[y]] ← x // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”
13 if y ≠ z
14 then key[z] ← key[y] // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色!!!
15 copy y's satellite data into z
16 if color[y] = BLACK
17 then RB - DELETE - FIXUP(T, x) // 若“y为黑节点”,则调用
18 return y
RB - DELETE - FIXUP(T, x)
01 while x ≠ root[T] and color[x] = BLACK
02 do if x = left[p[x]]
03 then w ← right[p[x]] // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子)
04 if color[w] = RED // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
05 then color[w] ← BLACK ? Case 1 // (01) 将x的兄弟节点设为“黑色”。
06 color[p[x]] ← RED ? Case 1 // (02) 将x的父节点设为“红色”。
07 LEFT - ROTATE(T, p[x]) ? Case 1 // (03) 对x的父节点进行左旋。
08 w ← right[p[x]] ? Case 1 // (04) 左旋后,重新设置x的兄弟节点。
09 if color[left[w]] = BLACK and color[right[w]] = BLACK // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。
10 then color[w] ← RED ? Case 2 // (01) 将x的兄弟节点设为“红色”。
11 x ← p[x] ? Case 2 // (02) 设置“x的父节点”为“新的x节点”。
12 else if color[right[w]] = BLACK // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。
13 then color[left[w]] ← BLACK ? Case 3 // (01) 将x兄弟节点的左孩子设为“黑色”。
14 color[w] ← RED ? Case 3 // (02) 将x兄弟节点设为“红色”。
15 RIGHT - ROTATE(T, w) ? Case 3 // (03) 对x的兄弟节点进行右旋。
16 w ← right[p[x]] ? Case 3 // (04) 右旋后,重新设置x的兄弟节点。
17 color[w] ← color[p[x]] ? Case 4 // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。(01) 将x父节点颜色 赋值给 x的兄弟节点。
18 color[p[x]] ← BLACK ? Case 4 // (02) 将x父节点设为“黑色”。
19 color[right[w]] ← BLACK ? Case 4 // (03) 将x兄弟节点的右子节设为“黑色”。
20 LEFT - ROTATE(T, p[x]) ? Case 4 // (04) 对x的父节点进行左旋。
21 x ← root[T] ? Case 4 // (05) 设置“x”为“根节点”。
22 else (same as then clause with "right" and "left" exchanged) // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。
23 color[x] ← BLACK
详细内容看
这里