1. 性质
- 每个结点或是红色的,或设黑色的。
- 根结点是黑色的。
- 每个叶子结点(NIL)是黑色的。
- 如果一个结点是红色的,则它的两个子结点都是黑色的。
- 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
其核心思想是,对于任意一个子树(当然,整棵树本身也是它自己的一个子树), 从子树的根结点到每个叶结点的简单路径上:
- 黑色结点数目相同。
- 不能有连续的红色结点(可以有连续的黑色结点)。
所以,最长路径是最短路径的2倍。比如,对于一个子树,从它的根到所有叶子的每条简单路径上,黑色结点数目是N+1(N个非NIL加上一个NIL),那么其中最多嵌入N个红色结点(每个非NIL黑色结点后都嵌入一个红色结点),否则,就会出现连续的红色结点。也就是说,最短路径长度为N+1(全是黑色结点),最长路径长度为2N+1(每个非NIL黑色结点后都有一个红色结点),是2倍的关系。这就是红黑树要达到的目标:接近平衡,而不会出现有的子树过高,有的子树过低,最多2倍。
需要注意一点:NIL是黑色的,也就是说,出现黑结点的地方,可能是NIL,在编程的时候需要小心。
2. 左旋转和右旋转
旋转的特点:保持二叉查找树的性质(按中根遍历,结点是有序的)。如何记忆:
以x为当前结点进行左旋转:x变成(它的右孩子的)左孩子(同时,保持二叉查找树的性质)。
以x为当前结点进行右旋转:x变成(它的左孩子的)右孩子(同时,保持二叉查找树的性质)。
3. 插入操作
插入操作的步骤是这样的:
- 按二叉查找树的算法,将目标结点插入;
- 把目标结点设置为当前结点,并把它着红色;
- 这时可能违背红黑树的性质,所以,进行修正;
其难点就在于如何修正。为了理解修正算法,我们先看看这时可能违背红黑树的哪些性质:
- 性质1:不违背;
- 性质2:若当前结点就是根结点(插入之前,是一棵空树),则违背;否则,不违背;
- 性质3:不违背;
- 性质4:若当前结点的父亲是红色,则违背;否则,不违背;
- 性质5:不违背,因为当前结点被着红色,所以,被插路径上的黑色结点数目不变;
也就是说,结点插入后,可能违背性质2和性质4,有一下几种情形:
- 违背性质2:当前结点为根结点(插入之前,是一棵空树)。这个容易修正,直接改着黑色就万事大吉。
- 不违背性质2,也不违背性质4:不违背任何性质,已经是一棵合法的红黑树,无须修正。
- 不违背性质2,但是违背性质4:需要修正。
下面就着重看第3种情形:违背性质4。也就是说,当前结点和它的父亲都是红色。这一共有以下3类6种(1-a, 1-b, 2-a, 2-b, 3-a, 3-b)情况:
3.1 类别1
类别1的特点是:父亲和叔叔都是红色的。注意,这种情况下,祖父结点一定存在,因为红结点一定有黑色的父亲结点。重申一下,我们违背的只有性质4:存在连续的红色结点。为了解决这个问题:
- 我们把父亲和叔叔都改为黑色,祖父改为红色(这样做可以保证:被影响的径上的黑色结点数目不变——一增一减,不至于违背性质5)。
- 把祖父设置为当前结点。
由于当前结点刚刚由黑色变为红色,可以把它看作新插入的结点,重头进行修正过程。若它一直违背类别1,则一直重复上面的操作,直到下面任意一种情况出现:
- 当前结点是根结点(违背性质2)。这时,直接把它改着黑色即可。因为,把根结点由红色改着黑色相当于:在每条由根到叶的路径上,黑结点数目都增加1,保持相等。修正结束。
- 当前结点的父亲是黑色的(不违背性质2,也不违背性质4)。这时已经是一棵合法的红黑树了,修正结束。
- 演变为类别2或者类别3(不违背性质2,但违背性质4)。
3.2 类别2
新结点插入后,可能处于类别2;也可能由类别1演变为类别2。类别2有2-a和2-b两种,但它们是对称的,所以只研究一种即可,我们单看2-a。它可以通过下图所示的步骤修正为合法红黑树。
为什么说,经过上面的修正,红黑树就合法了呢?
- 没有改变二叉查找树的性质(旋转操作不会改变这一性质);
- 由于新的子树的根(即图3中的结点4)为黑色,所以,不管x颜色如何,都不会出现连续的红色结点;
- 被影响的各条路径上,黑色结点数目保持不变。见下表(被影响的子路径的黑结点数目不变,那么它所在的整条路径的黑结点数目也不会变):
被影响的子路径 | 修正前(图1)黑色结点数 | 修正后(图3)黑色结点数 |
---|---|---|
…->x->p->… | 1 | 1 |
…->x->q->… | 1 | 1 |
…->x->r->… | 1 | 1 |
…->x->9->… | 2 | 2 |
综上所述:对于类别2,经这一步,就可以变为合法红黑树。
3.3 类别3
新结点插入后,可能处于类别3;也可能由类别1演变为类别3。类别3有3-a和3-b两种,但它们是对称的,所以只研究一种即可,我们单看3-a。它可以通过下图所示的步骤,修正为类别2(然后由类别2修正为合法的红黑树)。
需要注意的是,由类别3修正为类别2的过程中:
- 没有改变二叉查找树的性质(旋转操作不会改变这一性质);
- 被影响的路径上,黑色结点数目保持不变。见下表(被影响的子路径的黑结点数目不变,那么它所在的整条路径的黑结点数目也不会变):
被影响的子路径 | 修正前(图1)黑色结点数 | 修正后(图2)黑色结点数 |
---|---|---|
…->x->p->… | 1 | 1 |
…->x->q->… | 1 | 1 |
…->x->r->… | 1 | 1 |
…->x->8->… | 2 | 2 |
3.4 插入操作总结
可见,插入操作的修正过程:
- 若插入后处于类别1:最坏情况下,从插入位置(叶子)直到根,一直重复出现类别1,修正次数为树的高度。好一点的情况是,在到达根之前变为合法红黑树,或者演化为类别2或类别3;
- 若插入后处于类别2(或由1演化为类别2):经过一次修正,变为合法红黑树;
- 若插入后处于类别3(或由1演化为类别3):经一次修正演变为类别2,再由一次修正变为合法红黑树;
也就是说,最多改色次数为O(树的高度);最多旋转次数是2。