红黑树
五大性质
1)每个结点要么是红的,要么是黑的。
2)根结点是黑的。
3)每个叶结点(叶结点即指树尾端NIL指针或NULL结点)是黑的。
4)如果一个结点是红的,那么它的俩个儿子都是黑的。 不可能有两个连续的红
5)对于任一结点而言,其到叶结点树尾端NIL指针的每一条路径都包含相同数目的黑结点。
左旋
左旋就是 成为右孩子的左孩子
LeftRate(node x)
y=x.right;
x.right=y.left; y的左孩子成为x的右孩子
y.parent=x.parent; x的父节点成为y的父节点
y.left=x; x成为y的左孩子
x.parent=y;
右旋
成为左孩子的右孩子
RightRate(node x)
y=x.left;
x.left=y.right; y的右孩子成为x的左孩子
y.parent=x.parent; x的父亲 成为y的父亲
y.right=x; x成为y的右孩子
x.parent=y;
插入节点
如果插入的是根结点,由于原树是空树,此情况只会违反性质2,因此直接把此结点涂为黑色;
如果插入的结点的父结点是黑色,由于此不会违反性质2和性质4,红黑树没有被破坏,所以此时什么也不做。
父节点是黑色,什么也不做
● 插入修复情况1:如果当前结点的父结点是红色,叔叔是红色
● 插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子
● 插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子
RB-INSERT(T, z)
y ← nil
x ← T.root
while x ≠ T.nil
do y ← x
if z.key < x.key
then x ← x.left
else x ← x.right
z.p ← y
if y == nil[T]
then T.root ← z
else if z.key < y.key
then y.left ← z
else y.right ← z
z.left ← T.nil
z.right ← T.nil
z.color ← RED 插入的节点的颜色 最先是红色。
RB-INSERT-FIXUP(T, z)
插入修复
RB-INSERT-FIXUP(T, z)
while z.p.color == RED
do if z.p == z.p.p.left
then y ← z.p.p.right
if y.color == RED
then z.p.color ← BLACK ▹ Case 1 //父节点 叔叔节点都是红色
y.color ← BLACK ▹ Case 1 //那么父节点 叔叔节点全部变黑
z.p.p.color ← RED ▹ Case 1 // 爷爷节点变成红色
z ← z.p.p ▹ Case 1 //爷爷节点变成当前节点
else if z == z.p.right //如果当前节点(可能是之前爷爷节点,对应情况1)是父节点的右孩子,且父亲是红色,叔叔是黑色 ,那么就是情况2
then z ← z.p ▹ Case 2 //把当前节点的父节点作为当前节点
LEFT-ROTATE(T, z) ▹ Case 2 //再对当前节点进行左旋
z.p.color ← BLACK ▹ Case 3 //情况3当前节点的父节点是红色,叔叔是黑色,当前节点是父节点的左子
z.p.p.color ← RED ▹ Case 3 //把父节点变成黑色,爷爷变成红色
RIGHT-ROTATE(T, z.p.p) ▹ Case 3//再对爷爷右 旋。
else (same as then clause with "right" and "left" exchanged)
T.root.color ← BLACK //最后再把根节点涂黑
删除节点
① 被删除节点没有儿子,即为叶节点。那么,直接将该节点删除就OK了。
② 被删除节点只有一个儿子。那么,直接删除该节点,并用该节点的唯一子节点顶替它的位置。
③ 被删除节点有两个儿子。那么,先找出它的后继节点;然后把“它的后继节点的内容”复制给“该节点的内容”;之后,删除“它的后继节点”。在这里,后继节点相当于替身,在将后继节点的内容复制给"被删除节点"之后,再将后继节点删除。这样就巧妙的将问题转换为"删除后继节点"的情况了,下面就考虑后继节点。 在"被删除节点"有两个非空子节点的情况下,它的后继节点不可能是双子非空。既然"的后继节点"不可能双子都非空,就意味着"该节点的后继节点"要么没有儿子,要么只有一个儿子。若没有儿子,则按"情况① "进行处理;若只有一个儿子,则按"情况② "进行处理。 情况三 转移为删除后继节点
RB-DELETE(T, z) 删除z节点
1 if left[z] = nil[T] or right[z] = nil[T]
2 then y ← z
3 else y ← TREE-SUCCESSOR(z) y=z的后继节点,后继节点只可能有一个子节点,或者没有节点
4 if left[y] ≠ nil[T]
5 then x ← left[y] x为被删节点的左子节点
6 else x ← right[y] 否则x为被删节点的右子节点
7 p[x] ← p[y] 让y的父亲变成x的父亲 ,再让父亲指向 x就可以了
8 if p[y] = nil[T]
9 then root[T] ← x
10 else if y = left[p[y]]
11 then left[p[y]] ← x
12 else right[p[y]] ← x
13 if y ≠ z //把z的后驱节点找到以后 再把 y的值赋值给z 这样 z以前的值就被删了 然后再去
14 then key[z] ← key[y]
15 copy y's satellite data into z
16 if color[y] = BLACK 始终记住 y是被删的节点 x在y以前的位置,所以从x开始调整
17 then RB-DELETE-FIXUP(T, x)
18 return y
删除修复
“上面的修复情况看起来有些复杂,下面我们用一个分析技巧:我们从被删节点后来顶替它的那个节点开始调整,并认为它有额外的一重黑色。这里额外一重黑色是什么意思呢,我们不是把红黑树的节点加上除红与黑的另一种颜色,这里只是一种假设,我们认为我们当前指向它,因此空有额外一种黑色,可以认为它的黑色是从它的父节点被删除后继承给它的,它现在可以容纳两种颜色,如果它原来是红色,那么现在是红+黑,如果原来是黑色,那么它现在的颜色是黑+黑。有了这重额外的黑色,原红黑树性质5就能保持不变。现在只要恢复其它性质就可以了,做法还是尽量向根移动和穷举所有可能性。"
把当前节点 看成 红+黑 或者 黑黑 ,就可以保证性质5在删除一个节点后 不会改变,然后再恢复其他性质
不用修复的情况
- a)当前节点是红+黑色
解法,直接把当前节点染成黑色,结束此时红黑树性质全部恢复。 - b)当前节点是黑+黑且是根节点, 解法:什么都不做,结束。
删除修复情况1:当前节点是黑+黑且兄弟节点为红色(此时父节点和兄弟节点的子节点分为黑)
解法:把父节点染成红色,把兄弟结点染成黑色,之后重新进入算法(我们只讨论当前节点是其父节点左孩子时的情况)。此变换后原红黑树性质5不变,而把问题转化为兄弟节点为黑色的情况(注:变化前,原本就未违反性质5,只是为了把问题转化为兄弟节点为黑色的情况)
//调用RB-DELETE-FIXUP(T, x) 的1-8行代码
1 while x ≠ root[T] and color[x] = BLACK
2 do if x = left[p[x]]
3 then w ← right[p[x]]
4 if color[w] = RED
5 then color[w] ← BLACK ▹ Case 1
6 color[p[x]] ← RED ▹ Case 1
7 LEFT-ROTATE(T, p[x]) ▹ Case 1
8 w ← right[p[x]] ▹ Case 1
删除修复情况2:当前节点是黑加黑且兄弟是黑色且兄弟节点的两个子节点全为黑色
解法:把当前节点和兄弟节点中抽取一重黑色追加到父节点上,把父节点当成新的当前节点,重新进入算法。(此变换后性质5不变),即调用RB-INSERT-FIXUP(T, z) 的第9-10行代码操作
//调用RB-DELETE-FIXUP(T, x) 的9-11行代码
9 if color[left[w]] = BLACK and color[right[w]] = BLACK
10 then color[w] ← RED ▹ Case 2
11 x p[x] ▹ Case 2
删除修复情况3:当前节点颜色是黑+黑,兄弟节点是黑色,兄弟的左子是红色,右子是黑色
解法:把兄弟结点染红,兄弟左子节点染黑,之后再在兄弟节点为支点解右旋,之后重新进入算法。此是把当前的情况转化为情况4,而性质5得以保持,即调用RB-INSERT-FIXUP(T, z) 的第12-16行代码
//调用RB-DELETE-FIXUP(T, x) 的第12-16行代码
12 else if color[right[w]] = BLACK
13 then color[left[w]] ← BLACK ▹ Case 3
14 color[w] ← RED ▹ Case 3
15 RIGHT-ROTATE(T, w) ▹ Case 3
16 w ← right[p[x]] ▹ Case 3
删除修复情况4:当前节点颜色是黑-黑色,它的兄弟节点是黑色,但是兄弟节点的右子是红色,兄弟节点左子的颜色任意
解法:把兄弟节点染成当前节点父节点的颜色,把当前节点父节点染成黑色,兄弟节点右子染成黑色,之后以当前节点的父节点为支点进行左旋,此时算法结束,红黑树所有性质调整正确,即调用RB-INSERT-FIXUP(T, z)的第17-21行代码
//调用RB-DELETE-FIXUP(T, x) 的第17-21行代码
17 color[w] ← color[p[x]] ▹ Case 4
18 color[p[x]] ← BLACK ▹ Case 4
19 color[right[w]] ← BLACK ▹ Case 4
20 LEFT-ROTATE(T, p[x]) ▹ Case 4
21 x ← root[T] ▹ Case 4
此时,我们需要调用RB-DELETE-FIXUP(T, x),来恢复与保持红黑性质的工作。
1 while x ≠ root[T] and color[x] = BLACK
2 do if x = left[p[x]]
3 then w ← right[p[x]]
4 if color[w] = RED //情况1兄弟为红色 ,兄弟的孩子都是黑
5 then color[w] ← BLACK // 兄弟变黑 ▹ Case 1
6 color[p[x]] ← RED //父亲变红 ▹ Case 1
7 LEFT-ROTATE(T, p[x]) //父亲左旋 ▹ Case 1
8 w ← right[p[x]] //更新当前节点的兄弟 ▹ Case 1
9 if color[left[w]] = BLACK and color[right[w]] = BLACK//兄弟是黑,兄弟左右子都是黑
10 then color[w] ← RED //各抽一成黑给父亲,父亲变成红+黑 ▹ Case 2
11 x ← p[x] // 把父节点作为当前节点 ,当前节点是红+黑了 ▹ Case 2
12 else if color[right[w]] = BLACK //兄弟是黑 兄弟 的左子是红 右子是黑
13 then color[left[w]] ← BLACK //兄弟左子变成黑 ▹ Case 3
14 color[w] ← RED //兄弟变成红 ▹ Case 3
15 RIGHT-ROTATE(T, w) //以兄弟右旋 ▹ Case 3
16 w ← right[p[x]] //更新当前节点的兄弟 ▹ Case 3
17 color[w] ← color[p[x]] //兄弟的颜色改成父亲的颜色 ▹ Case 4
18 color[p[x]] ← BLACK //父亲的颜色变成黑 ▹ Case 4
19 color[right[w]] ← BLACK // 兄弟的右子变成黑色 ▹ Case 4
20 LEFT-ROTATE(T, p[x]) //以父亲左旋 ▹ Case 4
21 x ← root[T] //继续循环操作。 ▹ Case 4
22 else (same as then clause with "right" and "left" exchanged)
23 color[x] ← BLACK