原文转自:http://www.886s.com/blog/?p=30
组内培训,讲红黑树,找出算法导论,啃了一个周末,其中插入结点很简单,删除结点有点复杂,但跟着算法导论上一步一步来没有什么问题。不想备份blog的图片,所以没有把图片上穿。可直接察看ppt。
红黑树性质
1.每个节点或是红的,或是黑的
2.根节点是黑的
3.每个叶结点(NIL)都是黑的
4.如果一个结点是红的,则它的两个儿子都是黑的
5.对每个结点,从该节点到其子孙结点的所有路径上包含相同数目的黑结点
*从某个结点x出发(不包括该结点)到达一个叶结点的任意一条路径上,黑色结点的个数成为该结点x的黑高度,用bh(x)表示。
*引理:一颗有n个内节点的红黑树的高度之多为2lg(n+1)
插入不变式
a)结点z是红色。
b)如果p[z]是根,则p[z]是黑色。
c)如果有红黑树的性质被破坏,则至多只有一个被破坏,并且不是性质2)就是性质4)。如果违反性质2),则发生的原因是z是根而且是红的。如果违反性质4),则原因是z和p[z]都是红色的,则p[p[z]]必然是黑色的。
针对违反性质4)
①z的叔叔y是红色的
②z的叔叔y是黑色的,而且z是右孩子
③z的叔叔y是黑色的,而且z是左孩子
z的叔叔y是黑色的。这两种情况是通过z是p[z]的左孩子还是右孩子来区别。在情况2中,结点z是他的父亲的右孩子。我们立即使用一个左旋来将此情况转变为情况3,此时结点z成为左孩子。因为z和p[z]都是红色的,所以所做的旋转对结点的黑高度和性质5)都无影响。在情况3中,要改变某些结点的颜色,并作一次右旋以保持性质5。这样,由于在一行中不在有两个连续的红色结点,因而,所有的处理完毕,无需再次执行while循环。
二叉查找树删除
*二叉查找树删除(删除结点z)
如果z没有子女,则修改其父结点p[z],使NIL为其子女;
如果结点z只有一个子女,则可以通过在其子结点与父结点间建立一条链来删除z;
如果结点z有两个子女,先删除z的后继y,再用y的内容替代z的内容。
*后继
二叉树中,如果所有的关键字均不相同,则某一结点x的后继既具有大于key[x]中的关键字中最小者的那个结点。
红黑树删除
RB-DELETE(T,z)
1 if left[z] = nil[T] or right[z] = nil[T]
2 then y ← z
3 else y ← TREE-SUCCESSOR(z)
4 if left[y] ≠ nil[T]
5 then x ← left[y]
6 else x ← right[y]
7 p[x] ← p[y]
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
14 then key[z] ← key[y]
15 copy y’s satellite data into z
16 if color[y] = BLACK
17 then RB-DELETE-FIXUP(T, x)
18 return y
A.如果被删除的结点是红色的,则当结点被删除后,红黑性质仍然得以保持,理由如下:
a)树中各结点的黑高度都没有变化
b)不存在两个相邻的红色结点
c)因为如果该节点是红的,就不可能是根,所以跟仍然是黑色的
B.如果被删除的结点是黑色的,则会产生三个问题。
要删除的结点y,如果y有个不是NIL的孩子,则x为y的唯一孩子;如果y没有孩子,则x为NIL,把x的父节点(原来是y)赋值为y的父节点
①如果y原来是根结点,而y的一个红色的孩子成为了新的根,这就违反了性质2)。
②如果x和p[y](现在也是p[x])都是红的,就违反了性质4)。
③删除y将导致先前包含y的任何路径上黑结点个数少1。因此,性质5)被y的一个祖先破坏了。补救这个问题的一个办法就是把结点x视为还有额外的一重黑色。也就是说,如果将任意包含结点x的路径上黑结点的个数加1,则这种假设下,性质5)成立。当将黑节点y删除时,将其黑色“下推”至其子节点。现在问题就变为结点x可能既不是红,也不是黑,从而违反了性质1)。结点x是双重黑色或红黑,这就分别给包含x的路径上黑结点的贡献2个或1个。x的color属性仍然是red(如果x是红黑的)或者black(如果x是双重黑色)。换言之,一个结点额外的黑色反映在x指向它,而不是他的color属性。
删除算法
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
5 then color[w] ← BLACK Case1
6 color[p[x]] = RED Case1
7 LEFT-ROTATE(T,p[x]) Case1
8 w ← right[p[x]] Case1
9 if color[right[w]] = BLACK and color[right[w]= BLACK
10 then color[w] ← RED Case2
11 x ← p[x] Case2
12 else if color[right[w]] = BLACK
13 then color[left[w]] ← BLACK Case3
14 color[w] ← RED Case3
15 RIGHT-ROTATE(T,w) Case3
16 w ← right[p[x]] Case3
17 color[w] ← color[p[x]] Case4
18 color[p[x]] ← BLACK Case4
19 color[right[w]] ← BLACK Case4
20 LEFT-ROTATE(T,p[x]) Case4
21 x ← root[T] Case4
22 else (same as then clause with “right” and “left” exchanged)
23 color[x] ← BLACK
第1-22行中while循环的目的是将额外的黑色沿树上移,直到:
1.x指向一个红黑结点,将x(单独)着为黑色。
2.X指向根,这时可以简单地消除那个额外的黑色,或者
3.作必要的旋转和颜色修改
情况1:x的兄弟w是红色的
Þ 因为w必须有黑色的孩子,我们可以改变w和p[x]颜色,再对p[x]做一次左旋,从而红黑性质得以继续保持。现在,x的新兄弟是旋转之前w的某个孩子,其颜色为黑色。这样,我们已经将情况1)转化为情况2)3)或4)了。
情况2:x的兄弟w是黑色的,而且w的两个孩子都是黑色的
Þ 因为w也是黑色的,故从x和w上去掉一重黑色,从而x只有一重黑色而w为红色。为了补偿从x和w中去掉一重黑色,我们想在原来是红色或者黑色的p[x]内新增一重额外的黑色。通过以p[x]为新结点x来恢复while循环。注意如果从情况1进入情况2,则新结点x是红黑色的,因为原来的p[x]是红色的。因此,新结点x的color属性的值c为red,并且在测试循环条件后循环结束。然后新结点x在第23行中被(单独)着为黑色。
情况3:x的兄弟w是黑色的,w的左孩子是红色的,右孩子是黑色的
Þ 可以交换w和其左孩子left[w]的颜色,并对w进行右旋,而红黑性质仍然保持。现在x的新兄弟w是一个有红色右孩子的黑节点,这样我们从情况3转换成情况4
情况4:x的兄弟w是黑色的,而且w的右孩子是红色的
Þ 通过做某些颜色修改并对p[x]做一次左旋,可以去掉x的额外黑色来把它变成单独黑色,而不破坏红黑性质。将x置为根后,当while循环测试其循环条件时循环结束。
了解更多