参考:
《算法导论》
红黑树(一) 原理和算法详细介绍
浅谈算法和数据结构: 九 平衡查找树之红黑树
红黑树是一棵二叉搜索树,每个节点有一个标志位表示颜色,该颜色可以是红(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”
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; }
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