引入
由于二叉搜索树很不平衡,很容易退化为一条类似链表的结构,从而造成时间复杂度从
为解决这一问题,引入了“平衡”二叉搜索树,红黑树就是其中的一种。红黑树在树的基础上,为每个节点增加了一个颜色位,可以是RED
,也可以是BLACK
。通过对每条路径的颜色进行约束,保证红黑树处于近似平衡的状态,从而保证了查找最坏情况为
特性
红黑树除了本身是二叉搜索树外,还满足以下条件:
- 每个节点要么是红色的,要么是黑色的。
- 根节点是黑色的。
- 每个空节点(NIL)都是黑色的。
- 红色节点的两个子节点都是黑色的。
- 每个节点到其后代叶子节点的路径上包含的黑色节点数目相同。
红黑树的定义如下:
1 | enum RBTColor{RED, BLACK}; |
样例如图:
算法
旋转
由于结点的插入、删除操作对树进行了修改。修改后的红黑树可能不再满足上面所列出的红黑性质,故需要对树的某些结点进行旋转或变色使其自平衡。
忽略红黑特性,以一个二叉搜索树为例进行演示旋转。样例如下:
图中x y
是两个节点,a b c
表示任意子树。
不难看出,图中的旋转仍然保持了二叉搜索树的性质:a子树所有节点的key值小于x.key
;c子树所有节点的key值大于y.key
;b子树key值介于x.key
与y.key
之间。伪代码如下
1 | //左旋 |
旋转操作只改变所旋转元素附近的指针,其他属性均未改变。可以在常数时间
插入
由于红黑树对黑色节点的要求较为苛刻,新插入的节点默认为红色。
将某个节点插入到红黑树,详细过程如下:
- 首先,红黑树本身是一个二叉搜索树,依据其性质可以找到相应的插入位置,保证节点插入后key仍有序。
- 插入后,进行一系列的旋转、着色使其继续保持红黑特性。
正常情况下(排除红黑树从0开始构建的情况),当我们插入一个节点后如果违反了红黑树的红黑特性,那么所违反的应该为特性4(红节点的两个子节点均为黑色)。我们需要从当前节点向根节点逐层向上判断是否调整并作出相应的调整。相关调整方案如下:
最后,根节点恒染为黑色。
下面给出一个范例,显示一个红黑树上的调整操作。
在上图中,初始状态x
为插入的节点,其父节点(key = 5
)违背红黑规则4,需要进行调整:
- 由于
x
的父节点和叔节点都是红色(调整方案1.2),调整时需要将其父节点(key = 5
)和叔节点(key = 8
)染成黑色,将其祖父节点(key = 7
)染成红色,然后考虑其祖父节点。x节点
上移为其祖父节点(key = 7
),此时x节点
的父节点(key = 2
)违背红黑规则4 - 叔节点不为红色,调整策略对应为1.1。观察到父节点是祖父节点的左子树,自己是父节点的右节点(1.1.1.1)。当前节点上移为其父节点(
key = 2
)然后左旋。左旋后的x节点
执行调整方案1.1.1.2 - 父节点(
key = 7
)染成黑色,祖父节点(key = 11
)染成红色,将祖父节点右旋。x节点
的父节点(key = 11
)不再是红色,退出循环。此时当前红黑树不再违背红黑规则。 - 循环退出后,将其根节点染成黑色。
调整方案对应的伪代码如下:
1 | while x.parent.color == RED // 1 |
删除
删除和插入操作类似,也分为两个步骤:
- 红黑树本身是一个二叉搜索树,根据其性质寻找到对应节点并删除掉。删除的情况如下
- 若该节点是叶子节点,则直接删除。
- 若只有一个子树,则用子树替代当前节点。
- 若有两个子树,则用右子树的最小值(大于被删除元素的最小值)替代当前节点。
- 通过旋转、着色等操作将红黑树修正,使之成为新的红黑树。
根据所替代节点的相关信息来进行调整:
同样的,在循环结束之后,将当前节点染成黑色。
下面给出几个具体样例进行分析:
例一
在该样例中,A节点为黑色,且是其父节点的左节点,兄弟节点D为红色,满足调整方案1.1.1。所做出的调整为将其父节点
B
染成红色,兄弟节点D
染成黑色。然后将其父节点B
进行左旋。此后均满足条件。例二
在该样例中,A节点为黑色,且是其父节点的左节点,兄弟节点D为黑色,两个侄子节点
C
和E
均为黑色,满足调整方案1.1.2.1。调整时,将其兄弟结点D
染成红色,x
已上移为根节点B
,循环结束。需要特别注意的是,此刻应再将节点B
染成黑色。例三
在该样例中,A节点为黑色,且是其父节点的左节点,兄弟节点D为黑色,左侄子
C
为红色,右侄子E
为黑色,满足调整方案1.1.2.2。调整时,将其左侄子C
染成黑色,兄弟节点D
染成红色,并将兄弟节点D右旋,然后再按照调整方案1.1.2.3进行调整,在此略过。例四
在该样例中,A节点为黑色,且是其父节点的左节点,兄弟节点D为黑色,右侄子
E
为红色,满足调整方案1.1.2.3。调整时,兄弟节点D
颜色随父节点B
,右侄子E
和父节点B
染成黑色,将父节点B
左旋。
调整方案对应伪代码如下:
1 | while x != T.root and x.color == BLACK |
后记
红黑树插入、删除节点的情况虽然复杂,但基本处于对称情况,且有一个模型通过拼凑其他模型来实现。本文中的伪代码、样例均选自《算法导论》,红黑树的图采用drawio绘制。