1. 前言
一个红黑树是一种自平衡的二叉查找树。二叉树的每个节点都有一个额外的位,该位通常被解释为节点的颜色(红色或黑色)。这些颜色位用于确保树在插入和删除期间保持近似平衡。
通过以满足某些属性的方式用两种颜色之一绘制树的每个节点来保留平衡,这些属性共同限制树在最坏情况下的不平衡。修改树时,随后重新排列新树并重新绘制以恢复着色属性。这些属性的设计使得这种重新排列和重新着色可以有效地进行。
树的平衡并不完美,但它足以保证在O(log n)时间内进行搜索,其中n是树中元素的总数。插入和删除操作以及树重新排列和重新着色也在O(log n)时间内执行。
2. 性质
★重点 红黑树的五条性质
- 1.每个节点都是红色或黑色。
- 2.根是黑色的。有时会省略此规则。由于根始终可以从红色变为黑色,但不一定相反,因此该规则对分析几乎没有影响。
- 3.所有叶子(NIL)都是黑色的。
- 4.如果节点为红色,则其子节点均为黑色。
- 5.从给定节点到其任何后代NIL节点的每条路径都包含相同数量的黑色节点。
一些定义:从根到节点的黑节点数是节点的黑色深度 ; 从根到叶子的所有路径中的黑色节点的统一数量被称为红黑树的黑色高度。
这些约束强制执行红黑树的关键属性:从根到最远叶子的路径不超过从根到最近叶子的路径的两倍。结果是树大致高度平衡。
3. 主要通过分析TreeMap源码了解红黑树的插入和删除
主要分析的不是具体的插入和删除操作,而是插入和删除操作成功后节点颜色如何进行修整,以重新满足红黑树的五种性质。
3.1 插入
插入开始于以与标准二进制搜索树插入非常类似的方式添加节点并将其着色为红色。最大的区别在于,在二叉搜索树中,新节点被添加为叶子,而叶子在红黑树中不包含任何信息,因此新节点替换现有叶子,然后添加两个自己的黑叶子。
红黑树插入的几种情况:
说明:N是新插入的节点 P是N的父节点 U是P的兄弟节点也即是N的叔叔节点 N为新插入节点默认是红色的
情况1. N是根节点
方案:直接将N变成黑色即可 满足性质2 也不违反性质5
情况2. P是黑色节点
方案:什么都不用做 每个红色节点的两个子节点都是黑色的,所以性质4不会失效 性质5也不会有影响
情况3. P是红色节点,U也是红色节点
方案:将P和U变成黑色,将祖父节点G变成红色,性质5依然保持,但是祖父节点G有可能违反性质2,如果G就是根节点,就把G变成黑色,否则进行向上递归调整。
情况4. 步骤一:P是红色节点,U是黑色节点,N是P的右子节点。
方案:先对N进行左旋(将任意节点通过旋转变成其右子节点的左子节点)操作,然后进行步骤二继续调整。
情况4. 步骤二:P是红色节点,U是黑色节点,N是P的左子节点。
方案:对G进行右旋,调整P和G的位置并互换颜色,此时性质4被恢复,性质5也没有受到影响。
代码来自JDK11 TreeMap源码,为了方便与图示进行参照,特意改了一下变量名
3.2 删除
在删除具有两个非叶子节点的节点D时,在常规二叉搜索树中,我们找到其左子树中的最大元素或其右子树中的最小元素X,然后移动X的值写入被删除节点D中,最后将X节点删除,这样就将要删除有两个孩子节点的情况转化为删除只有一个孩子节点的情况,至于最终删除的是节点X,而不是最初要删除的D并无大碍,D的值已经删除了,X的值也移到了D节点只是红黑树内部结构变化了,数据并没有丢失。接下来讨论的就是删除节点后,红黑树颜色调整问题,需要注意的是现在X只有一个孩子节点N。
首先声明要删除的节点是X,X的孩子节点是N,
现在N替换了X的位置,
N的父节点是P并且N是P的左孩子节点,
N的兄弟节点是S,
S的左孩子节点是SL,S的右孩子节点是SR
先来粗略讨论几种大概的情况:
- 删除节点X是红色。 因为X是红色,根据性质4,N和P必定是都是黑色,直接删除X并不会影响性质5
- 删除节点X是黑色,子节点N是红色。 将N变成黑色即可
- 删除节点X是黑色,子节点N也是黑色。 此时比较复杂,可以再细分为以下5种情况。
情况1. N是新的根节点
方案:这种情况删除了原先的根,只剩一个新根N,满足性质2
情况2. S是红色
方案:交换S和P的颜色,并将P左旋
然后N的兄弟节点变成了SL,此时SL一定是黑色(它曾是红色节点S的孩子),然后以P为根节点的子树继续向下递归处理
情况3. S和S的孩子节点都是黑色 P可红可黑
方案:将S变成红色,如果P为红色,就将P也变成黑色
然后会将N变成它的父节点P,向上进行递归处理,如果P是红色,结束平衡过程,将P变成黑色;如果P是黑色,然后重新执行平衡过程,从情况1开始
如果此时P是红色,正好符合情况2处理之后的情形
情况4. S是黑色 S的左孩子是红色 S的右孩子是黑色
方案:在S处右旋,并将S节点置黑,SL节点置红,继续情况5进行处理
情况5. S是黑色 S的右孩子是红色 S的左孩子可红可黑
方案:将SR置黑,并交换S和P的颜色,然后再P节点处进行左旋
这样S→N弥补了删除的一个黑色节点,S→SR和S→L的黑色节点数不变
下面代码也是来自JDK11 TreeMap源码,为了方便与图示进行参照,特意改了一下变量名
总结
相信根据代码逻辑和图的对照进行思考,红黑树的插入删除操作之后的平衡调整也就不在话下了
参考
- 红黑树 – 维基百科
- 红黑树详细分析