1. 红黑树概述
红黑树,英文名 Red-Black Tree,是一个自平衡的二叉搜索树。以前也叫做平衡二叉B树(Symmetric Binary B-tree)。
红黑树必须满足以下 5 条性质:
1. 每个节点是红色或着黑色的;
2. 树的根节点始终是黑色的;
3. 每个叶子节点(外部节点, null节点)是黑色;
注意:这里的叶子节点和之前系列文章的叶子节点不同,这里是指额外添加的 null 节点。如 下图1 中的 null节点 4. 每个红色结点的子结点都是黑色;
也就是说:红色节点的父节点都是黑色;
从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点.
5. 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点。
下图即为一棵红黑树:
注:本文后面所有示图均不画出 null 节点。
红黑树中用到的几个常用的术语:
parent:父节点
sibling:兄弟节点
uncle:叔父节点(parent的兄弟节点)
grand:祖父节点(parent的父节点)
2. 红黑树和4阶B树
2.1 红黑树和4阶B树的对比
可以通过 4阶B树 来更好的理解红黑树。
先上图,对比红黑树和 4阶B树
如上图所示:
对于红黑树,我们将黑色节点和它的红色子节点合并在一起(或者说将红色(兄弟)节点向上同它(们)的父节点合并在一起)就可以得到一个 4阶B树 节点。
无红色子节点的黑色节点单独为一个B树节点。
在对比红黑树和 4阶B树 时,在相互转化过程中,我们约定:
- 只有一个元素的节点,节点标黑;
- 有两个元素的,先加入该节点的标黑,后加入的标红。当转化为红黑树时,可分裂为两个节点,并将标红的节点作为标黑节点的子节点;
- 有三个元素的,中间元素标黑,左右元素标红。当转化为红黑树时,分裂为3个节点,并将左右两个红节点作为黑节点的子节点。
因此,上图中右下角的 4阶B树
,按照上述约定,可以转化为右上角的红黑树(调整展示样式即为左上角的红黑树)。同时右上角的红黑树也可以转化为 4阶B树
。
2.2 红黑树和4阶B树具有等价性
- 红黑树中,黑节点和它的红色子节点融合在一起,形成一个B树节点。
- 红黑树的黑节点个数与4阶B树的节点总数相等。
红黑树 vs 4阶B树
中,红黑树中节点合并得到 4阶B树
的几种情况:
1) 黑色节点和它的两个红色子节点合并成一个 4阶B树 节点。此时即为:红黑红。
2) 黑色节点和它仅有的右红色子节点合并成一个 4阶B树 节点。此时即为:黑红。
3) 黑色节点和它仅有的左红色子节点合并成一个 4阶B树 节点。此时即为:红黑。
4) 黑色节点没有红色子节点,自己独立成一个 4阶B树 节点。此时即为:黑。
下图分别展示了四种情况:
3. 红黑树的添加
基于红黑树和4阶B树的等价性,在红黑树添加元素时,可以对比 4阶B树
,方便分析其处理逻辑。
我们知道,在B树中,新元素必定是添加到叶子节点中,节点元素对应红黑树中的各节点。
在红黑树中,我们将新添加的节点默认设置为红色,然后再根据不同情况处理相关节点,并最终恢复红黑树。
默认设为红色,则直接满足了上面红黑树的性质1、2、3、5(性质4不应定满足),这样能够让红黑树的性质尽快满足。即只需要执行相关操作满足在此基础上满足性质4即可。
由于新元素必定是添加到叶子节点中,所以红黑树添加元素的共有 12 种情况,如下图所示:
在这 12 种情况中,其中 ⑤⑩⑪⑫ 四种情况的父节点为黑色,添加后直接满足性质4,故这四种情况直接添加即可。
剩下的 8 种情况,则不满足性质4,因为添加节点是红色,添加节点的父节点也是红色,即连续两个红色节点。
接下来一一分析各种添加场景的处理。
红黑树添加元素可以分为以下几种场景:
3.1 红黑树为空树
把插入节点作为根节点,并将节点染成黑色。
3.2 添加节点的父节点为黑色节点
对应上面 “添加的所有情况” 图中的 ⑤⑩⑪⑫。 直接添加即可。
3.3 添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在)时
对应上面 “添加的所有情况” 图中的 ⑥⑦⑧⑨。但这 4 种情况处理流程有所差异,接下来对这 4 种情况做相应的分析:
3.3.1 添加节点是父节点的左子节点,且父节点是祖父节点的左子节点,即对于祖父节点来说为 LL
即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的左子节点,父节点是祖父节点的左子节点。
方案:
1) 父节点染成黑色,祖父节点染成红色;
2) 对祖父节点进行右旋(LL - 右旋)。
示例:以下图做分析
针对上图分析:类比 4阶B树
,添加 60 的时候,就 4阶B树
来讲,60、72、76 三个元素合并成一个节点,经过之前 4阶B树
和红黑树的等价性的分析,可知,60 想要添加成功并且 60、72、76 三个元素想要合并成一个接点的话,72 此时必须要变成黑色,76 要变成红色,即 红(60)黑(72)红(76) ;同时红黑树转换成 4阶B树
时:黑色节点和其红色子节点合并成B树节点,也就是说此时的黑色节点 72 需要成为 60 和 76 两个红色节点的父节点,所以当 72 变成黑色之后,76 的父节点需要指向 72,此时对于祖父节点 76 来说相当于执行了一次右旋操作。
整个过程可总结为:父节点 72 染成黑色,祖父节点 76 染成红色;然后对祖父节点进行右旋。最终得到上图下半部分的结果图。
3.3.2 添加节点是父节点的左子节点,且父节点是祖父节点的右子节点,即对于祖父节点来说为 RR:
即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的左子节点,且父节点是祖父节点的右子节点。
这种情况和上面 3.3.1 的情况刚好相反,所以操作思路是一样的,只不过旋转操作相反。
方案:
1) 父节点染成黑色,祖父节点染成红色;
2) 对祖父节点进行左旋(RR - 左旋)。
如下图:添加节点 52 对于祖父节点 46 来说,相当于 RR,所以将父节点 50 染成黑色,祖父节点 46 染成红色;然后对祖父节点执行左旋操作,即得到下半部分的结果图
3.3.3 添加节点是父节点的右子节点,且父节点是祖父节点的左子节点,即对于祖父节点来说为 LR:
即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的右子节点,且父节点是祖父节点的左子节点。
分析:此时只需对父节点进行左旋转,即可转换为上面的 LL 型,所以:
方案:
1)对父节点进行左旋转,转换成 LL 型;
2)再按照 LL 型进行后续操作。
示例图如下:
上图,添加节点 74 是父节点 72 的右节点,72 是祖父节点 76 的左节点,即:对于祖父节点 76 来说,相当于 LR,如图左上角那个视图。
先将父节点 72 进行左旋,此时 72 成为 74 的左子节点,74 成为 76 的左子节点,经过旋转,此时已转换成 LL 型:如左下角的视图。
按照上面 场景2.1. LL 型,对此时的父节点 74 染黑,对此时的祖父节点 76 染红,然后对 76 右旋转,得到最终的结果,如右下角视图。
其实还可以先着色,然后再进行旋转,同样可以恢复成红黑树 即:
i)添加节点染成黑色,祖父节点染成红色;
ii)对父节点进行左旋,再对祖父节点右旋。
3.3.4 添加节点是父节点的左子节点,且父节点是祖父节点的右子节点,即对于祖父节点来说为 RL:
即:添加节点的父节点为红色,且添加节点的叔父节点不为红色(叔父节点黑色或者不存在),且添加节点是父节点的左子节点,且父节点是祖父节点的右子节点。
分析:此时只需对父节点进行右旋转,即可转换为上面的 RR 型,类似于上面的场景3.3.3,只不过方向相反,所以其旋转执行反向操作,后再做调整。所以:
方案:
1)对父节点进行右旋转,转换成 RR 型;
2)再按照 RR 型进行后续操作。
同样也可以先着色,然后再进行旋转,同样可以恢复成红黑树 即:
i)添加节点染成黑色,祖父节点染成红色;
ii)对父节点进行右旋,再对祖父节点左旋。
3.4 添加节点的父节点为红色,且添加节点的叔父节点为红色
对应上面所有情况中的 ①②③④,以情况①做分析
如下图:
添加节点 10,其父节点 17 为红色,同时其叔父节点 33 也为红色。此时对比 4阶B树 而言,10、17、25、33 合并成一个B树节点,出现上溢(节点4个元素超过4阶B树单个节点元素上线3的限制),因此按照 B树 上溢的操作,节点内元素向上合并且左右节点分裂,此时选择 25 上溢(不选17是因为,25 本身就是 38 的子节点,这样会方便处理),同时其左右分裂为两个子节点,左节点10和17想要成为一个B树节点(时刻注意类比红黑树),则17着黑色才能同 10 合并成一个节点,因为红黑树合并成一个B树节点的情况是:黑节点和其红色子节点合并。同样右边 33 需要着黑色。此时我们完成了分裂的逻辑。继续分析 25 向上合并,此时可以看做38 和 55 新加一个25,这个时候我们可以把 25 当做新加的节点,这样就回到了新加节点的判断处理流程。此处 25 的父节点为红色,且 25 的叔父节点不为红色(没有叔父节点,红黑树中默认 nil 节点为黑色),同时 25 是父节点 38 的左节点,父节点 38 是祖父节点55 的左节点,这样就符合上面 场景2.1. ,所以此时将父节点 38 染成黑色,祖父节点 55 染成红色,然后对对祖父节点 55 进行右旋。最红恢复红黑树平衡。
其过程如图中的标注信息。
所以,整个过程可归纳为:
1)将父节点和叔父节点染成黑色;
2)祖父节点向上合并:把祖父节点当做新添加的节点进行处理。
步骤祖父节点向上合并的时候,执行 1)可能还没有恢复红黑树,则继续递归处理。如果一直处理到根节点,则只需将根节点染成黑色
对于②③④的处理与①的处理流程一样,只不过其中涉及的旋转可能有所不同,因此不再赘述。
4. 删除
在B树中,最后真正被删除的元素都在叶子节点中。
当我们在B树中找到要删除的元素时,都会找到其后继元素,用后继元素替换其值,然后在删除后继节点。
同添加节点一样,红黑树的删除也先分为两大类:删除的节点是红色节点和删除的节点是黑色节点。
4.1 删除的节点是红色节点
直接删除也满足红黑树的性质四,故直接删除即可
注意:直接删除后,同样需要将删除的节点染成红色
4.2 删除的节点是黑色节点
删除节点是黑色节点又可以分为三类
1)黑色节点无子节点(即叶子节点)
2)黑色节点有一个红色子节点
3)黑色节点有两个红色子节点。
其中3)黑色节点又两个红色子节点是不可能别直接删除的,因为会找到其后继节点替换值后再删除后继结点,所以这种情况不考虑。
而 2) 又如何判断黑色节点有一个红色子节点呢?
通过用以取代该节点的子节点是否为红色。
我们知道二叉搜索树中,删除度为1的节点的时候,是用被删除的节点的子节点替换该节点位置的。
4.2.1 删除仅有一个红色子节点的黑色节点
删除仅有一个红色子节点的黑色节点有两种情况:如下图
1)红色子节点为右子节点 红色节点 50 为黑色节点 46 的右节点。
2)红色子节点为左子节点 红色节点 72 为黑色节点 76 的右节点。
上面两种情况的操作基本一样,所以选择其中之一来分析。
我们选择红色子节点为右子节点的情况来分析:整个过程如下图所示:
所以: 对于 删除仅有一个红色子节点的黑色节点 将替代的子节点染成 黑色 即可恢复红黑树平衡。
4.2.2 删除节点为黑色叶子节点,且其兄弟节点也为黑色
如下图的左半部分视图中的88 和右半部分视图中的 88,自己为黑色,且小弟节点76 也为黑色。
我们同样类比4阶B树进行分析,当我们删除左右两个视图中的 88 的时候,会导致B树下溢,需要向兄弟节点 “借” 元素来填补(兄弟节点元素向上合并,父节点元素向下填充)。可以发下当删除 左侧中的 88 时,其兄弟节点 76 有元素可借(对于B树而言,76 和 78 两个元素合并成一个B树节点)。而 右侧的 88,其兄弟节点 76是单个元素节点,无元素可以借。 针对兄弟节点有无元素可借,需要做不同的处理。因此 删除节点为黑色叶子节点,且其兄弟节点也为黑色 的场景需要再细分。
4.2.2.1 删除节点为黑色叶子节点,且其兄弟节点也为黑色,且兄弟节点至少有一个红色子节点
兄弟节点至少有一个红色子节点有三种情况,即红色右子节点、红色左子节点、以及两个红色子节点,如下图所示:
这里画的三种情况是以红色子节点的情况为标准进行划分的。删除节点和兄弟节点位置也可以与图中的情况相反,即88 是左子节点,76 是右子节点,与图中刚好相反(影响后续的旋转方向)。
我们以红色右子节点(上图最左边的视图)为例进行分析:
同样类比B树进行:
1)删除节点 88,B树下溢;
2)向兄弟节点借元素,元素 78 向上合并,父节点 80 向下填充。此时如果以78 为参照,对于 80来说,刚好符合 LR(78是父节点76的右节点,76是祖父节点的左节点),因此对 76 执行左旋转,对 80执行右旋转,解决下溢的问题;
3)对旋转之后的中心节点染为最初删除节点的父节点的颜色,同时旋转之后的新的左右子节点然黑色(76 和 80 要想成为一个独立的 B 树节点,则必须为黑色)。
整个过程和结果如下图所示:
另外两种删除情况类似,不在赘述。
4.2.2.2 删除节点为黑色叶子节点,且其兄弟节点也为黑色,且兄弟节点没有一个红色子节点
当然如果删除节点的父节点为黑色,其处理会稍有不同:
4.2.3 删除节点为黑色叶子节点,且其兄弟节点为红色
从B树的角度来看,兄弟节点为红色,则必然无元素可借。
红黑树的黑色节点和其红色子节点合并成4阶B树。
如果直接从该红色节点的子节点借元素,则会比较麻烦。我们可以想办法将红色兄弟节点的子节点(必然是黑色)变成兄弟节点,这样就转换成了 删除节点为黑色叶子节点,且其兄弟节点也为黑色的情况。
1)将兄弟节点染成黑色,同时将父节点染成红色;
2)对父节点进行相应旋转;
3)转换为 删除节点为黑色叶子节点,且其兄弟节点也为黑色 的情况,按照对应的操作,最终恢复红黑树平衡。
示例:如下图所示
删除黑色叶子节点 88 的兄弟节点为红色,倘若我们删除 88,出现下溢,而其兄弟节点 55 无元素可借。我们将兄弟节点 55 染黑,然后将父节点 80 染红。再对父节点 80 进行右旋操作,此时已转换成上面所分析处理的 删除节点为黑色叶子节点,且其兄弟节点也为黑色 的情况(如图右下角部分视图),按照该情况的处理,最终可以是红黑树恢复平衡(如图左下角部分视图)。
5. 添加删除代码
5.1 红黑树节点
public static class RBTreeNode<E> {
boolean color = RED; //添加节点默认红色
E element; //节点数据
RBTreeNode<E> left; //左节点
RBTreeNode<E> right; //右节点
RBTreeNode<E> parent; //父节点
public RBTreeNode(E element, RBTreeNode<E> parent) {
this.element = element;
this.parent = parent;
}
public boolean hasTwoChildren() {
return left != null && right != null;
}
/**
* 是否是左子节点
*/
public boolean isLeftChild() {
return (parent != null && this == parent.left);
}
/**
* 是否是右子节点
*/
public boolean isRightChild() {
return (parent != null && this == parent.right);
}
/**
* 获取节点的兄弟节点
* @return
*/
public RBTreeNode<E> sibling() {
if (isLeftChild()) {
return parent.right;
}
if (isRightChild()) {
return parent.left;
}
return null;
}
}
复制代码
5.2 添加删除全部完整代码
/**
* 红黑树
* @param <E> 泛型
*/
public class RBTree<E> {
// 标记节点颜色
private static final boolean RED = false;
private static final boolean BLACK = true;
private int size; // 树元素大小
private RBTreeNode<E> root; // 根节点
private Comparator<E> comparator; // 比较器,可由外部控制比较器规则
// 外部不传入比较器规则,则使用默认的存入节点的对象实现的比较规则
public RBTree() {
this(null);
}
// 外部传入比较器规则,则使用传入的比较规则
public RBTree(Comparator<E> comparator) {
this.comparator = comparator;
}
/**
* 添加元素
*/
public void add(E element) {
elementNotNullCheck(element);
// 添加根节点
if (root == null) {
root = new RBTreeNode<>(element, null);
size++;
return;
}
// 添加的不是第一个节点
// 1.找到父节点
RBTreeNode<E> parent = root;
RBTreeNode<E> node = root;
int cmp = 0;
while (node != null) {
// 保存父节点,如果此时不保存的话,在 while 循环发现 node == null 的时候,跳出循环
parent = node;
cmp = compare(element, node.element);
if (cmp > 0) {
node = node.right;
} else if (cmp < 0) {
node = node.left;
} else { // 相等
node.element = element;
return;
}
}
// 2.看看插入到父节点的哪个位置
RBTreeNode<E> newNode = new RBTreeNode<>(element, parent);
if (cmp > 0) {
parent.right = newNode;
} else {
parent.left = newNode;
}
size++;
}
/**
* 添加节点后恢复红黑树平衡
* @param node 新添加的节点
*/
private void fixAfterAdd(RBTreeNode<E> node) {
RBTreeNode <E> parent = node.parent;
// 添加的是根节点
if (parent == null) {
black(node); //根节点着黑色
return;
}
// 插入节点的父节点是黑色,直接返回(因为插入的节点默认是红色)
if (isBlack(parent)) {
return;
}
/* 插入节点的父节点是红色 */
// 获取节点的叔父节点(父节点的兄弟节点)
RBTreeNode<E> uncle = parent.sibling();
// 获取节点的祖父节点(父节点的父节点)
RBTreeNode<E> grand = parent.parent;
// 叔父节点是红色
if (isRed(uncle)) {
black(parent);
black(uncle);
// 把祖父节点当做是新添加的节点,继续向上恢复红黑树
red(grand);
fixAfterAdd(grand);
return;
}
// 能来到这里,说明叔父节点不是红色
if (parent.isLeftChild()) { // L
if (node.isLeftChild()) { // LL
black(parent);
red(grand);
rotateRight(grand);
} else { // LR
red(grand);
black(node);
rotateLeft(parent);
rotateRight(grand);
}
} else { // R
if (node.isLeftChild()) { // RL
black(node);
red(grand);
rotateRight(parent);
rotateLeft(grand);
} else { // RR
black(parent);
red(grand);
rotateLeft(grand);
}
}
}
/**
* 删除元素
*/
public void remove(E element) {
// 通过元素值查找节点
RBTreeNode<E> node = node(element);
if (node == null) return;
size--;
if (node.hasTwoChildren()) { // 度为2的节点
// 找到后继结点
RBTreeNode<E> s = successor(node);
// 用后继结点的值覆盖读为2的节点的值
node.element = s.element;
// 删除后继结点,这里只需要将node指向s,后面代码统一删除 node 即可
node = s;
}
// 删除 node 节点(此时 node 节点的度必然是 0 或者 1,因为node的度未2的情况,上面已经特殊处理了)
RBTreeNode<E> replaceNode = node.left != null ? node.left : node.right;
if (replaceNode != null) { // node 是度为1的节点,使用子节点替代replaceNode替换node的位置
// 更改 parent
replaceNode.parent = node.parent;
if (node.parent == null) { // 删除的是根节点
root = replaceNode;
}
// 更改parent的left、right指向
if (node == node.parent.left) {
node.parent.left = replaceNode;
} else if (node == node.parent.right) {
node.parent.right = replaceNode;
}
// 删除节点之后的处理
fixAfterRemove(replaceNode);
} else if(node.parent == null) { // node是叶子节点且根节点
root = null;
// 删除节点之后的处理
fixAfterRemove(replaceNode);
} else { // node 是叶子节点但不是根节点
if (node == node.parent.right) {
node.parent.right = null;
} else {
node.parent.left = null;
}
// 删除节点之后的处理
fixAfterRemove(node);
}
}
/**
* 删除元素后,恢复红黑树平衡
* @param node 被删除的节点 或者 用以取代被删除节点的子节点(当被删除节点的度为1)
*/
private void fixAfterRemove(RBTreeNode<E> node) {
// 如果删除的节点是红色
// 或者 用以取代删除节点的子节点是红色
if (isRed(node)) {
black(node);
return;
}
RBTreeNode<E> parent = node.parent;
// 删除的是根节点
if (parent == null) return;
// 删除的是黑色叶子节点【下溢】
// 判断被删除的node是左还是右
boolean left = parent.left == null || node.isLeftChild();
RBTreeNode<E> sibling = left ? parent.right : parent.left;
if (left) { // 被删除的节点在左边,兄弟节点在右边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateLeft(parent);
// 更换兄弟
sibling = parent.right;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
fixAfterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.right)) {
rotateRight(sibling);
sibling = parent.right;
}
color(sibling, colorOf(parent));
black(sibling.right);
black(parent);
rotateLeft(parent);
}
} else { // 被删除的节点在右边,兄弟节点在左边
if (isRed(sibling)) { // 兄弟节点是红色
black(sibling);
red(parent);
rotateRight(parent);
// 更换兄弟
sibling = parent.left;
}
// 兄弟节点必然是黑色
if (isBlack(sibling.left) && isBlack(sibling.right)) {
// 兄弟节点没有1个红色子节点,父节点要向下跟兄弟节点合并
boolean parentBlack = isBlack(parent);
black(parent);
red(sibling);
if (parentBlack) {
fixAfterRemove(parent);
}
} else { // 兄弟节点至少有1个红色子节点,向兄弟节点借元素
// 兄弟节点的左边是黑色,兄弟要先旋转
if (isBlack(sibling.left)) {
rotateLeft(sibling);
sibling = parent.left;
}
color(sibling, colorOf(parent));
black(sibling.left);
black(parent);
rotateRight(parent);
}
}
}
/**
* 左旋转 - RR 型
*/
private void rotateLeft(RBTreeNode<E> node) {
RBTreeNode<E> subNode = node.right;
RBTreeNode<E> subSubNode = subNode.left;
node.right = subSubNode;
subNode.left = node;
// 让subNode成为最小失衡子树的根节点
subNode.parent = node.parent;
if (node.isLeftChild()) {
node.parent.left = subNode;
} else if (node.isRightChild()) {
node.parent.right = subNode;
} else {
root = subNode;
}
// 更新 subSubNode 的 父节点
if (subSubNode != null) {
subSubNode.parent = node;
}
// 更新 node 的父节点
node.parent = subNode;
}
/**
* 右旋转 - LL 型
*/
private void rotateRight(RBTreeNode<E> node) {
RBTreeNode<E> subNode = node.left;
RBTreeNode<E> subSubNode = subNode.right;
node.left = subSubNode;
subNode.right = node;
// 让subNode成为最小失衡子树的根节点
subNode.parent = node.parent;
if (node.isLeftChild()) {
node.parent.left = subNode;
} else if (node.isRightChild()) {
node.parent.right = subNode;
} else {
root = subNode;
}
// 更新 subSubNode 的 父节点
if (subSubNode != null) {
subSubNode.parent = node;
}
// 更新 node 的父节点
node.parent = subNode;
}
/**
* 通过值找到节点
*/
private RBTreeNode<E> node(E element) {
RBTreeNode<E> node = root;
while (node != null) {
int cmp = compare(element, node.element);
// 相等(即找到)直接返回
if (cmp == 0) return node;
// 查询元素比节点值大,则继续查找节点的右子树
if (cmp > 0) {
node = node.right;
} else {
node = node.left;
}
}
// while 循环结束,未查到对应节点
return null;
}
/**
* 获取一个节点的后继结点
*/
private RBTreeNode<E> successor(RBTreeNode<E> node) {
if (node == null) return null;
// 右子树不为空,前驱结点在右子树中(right.left.left...)
if (node.right != null) {
RBTreeNode<E> p = node.right;
while (p.left != null) {
p = p.left;
}
return p;
}
// 右子树为空,从父节点、祖父节点..中寻找前驱结点
while (node.parent != null && node == node.parent.right) {
node = node.parent;
}
return node.parent;
}
/**
* 节点着色
* @param node 着色节点
* @param color 需要着的颜色
* @return 返回着好色的节点
*/
private RBTreeNode<E> color(RBTreeNode<E> node, boolean color) {
if (node == null) return node;
node.color = color;
return node;
}
/**
* 节点着红色
* @param node 着红色的节点
* @return
*/
private RBTreeNode<E> red(RBTreeNode<E> node) {
return color(node, RED);
}
/**
* 节点着黑色
* @param node 着黑色的节点
* @return
*/
private RBTreeNode<E> black(RBTreeNode<E> node) {
return color(node, BLACK);
}
/**
* 获取节点的颜色
*/
private boolean colorOf(RBTreeNode<E> node) {
return node == null ? BLACK : ((RBTreeNode<E>)node).color;
}
/**
* 节点是否为红色
*/
private boolean isRed(RBTreeNode<E> node) {
return colorOf(node) == RED;
}
/**
* 节点是否为黑色
*/
private boolean isBlack(RBTreeNode<E> node) {
return colorOf(node) == BLACK;
}
/**
* 元素空(null)检查
* @param element 元素
*/
private void elementNotNullCheck(E element) {
if (element == null) {
throw new IllegalArgumentException("element must not be null");
}
}
/**
* 节点值比较
* @param e1
* @param e2
* @return 返回值等于0,代表e1 == e2;返回值大于0,代表e1 > e2;返回值小于0,代表e1 < e2;
*/
private int compare(E e1, E e2) {
// 外部传入了可比较器则使用传入的可比较器
if (this.comparator != null) {
return comparator.compare(e1, e2);
}
// 外部未可比较器则使用e1对应类实现的Comparable的比较器,因为二叉搜索树的节点必须存放可比较的对象元素
return ((Comparable<E>)e1).compareTo(e2);
}
}
复制代码
6. 红黑树的平衡和复杂度
文章开始的时候,列举了红黑树的5条性质,那么为何那5条性质,可以保证红黑树时平衡的呢?
那5条性质 可以保证红黑树和4阶B树等价
相比于AVL树,红黑树的平衡标准比较宽松:没有一条路径会大于其他路径的两倍
红黑树时一种弱平衡、黑高度平衡。
另外,红黑树的最大高度是 2 * log2(n + 1),依然是O(logn)。
红黑树的复杂度
搜索:O(logn)
添加:O(logn), O(1)次的旋转操作
删除:O(logn), O(1)次的旋转操作
AVL 树和红黑树的对比
- AVL 树 1) 平衡标注比较严格:每个左右自述的高度差不超过1;
2) 最大高度是 1.44 ∗ log2 n + 2 − 1.328(100W个节点,AVL树最大树高28);
3) 搜索、添加、删除都是 O(logn) 复杂度,其中添加仅需 O(1) 次旋转调整、删除最多需要 O(logn) 次旋转调整。
- 红黑树 1) 平衡标准比较宽松:没有一条路径会大于其他路径的2倍;
- 最大高度是 2 ∗ log2(n + 1)( 100W个节点,红黑树最大树高40);
3) 搜索、添加、删除都是 O(logn) 复杂度,其中添加、删除都仅需 O(1) 次旋转调整。
AVL 树和红黑树的选择
- 搜索的次数远远大于插入和删除,选择AVL树;搜索、插入、删除次数几乎差不多,选择红黑树;
- 相对于AVL树来说,红黑树牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树;
- 红黑树的平均统计性能优于AVL树,实际应用中更多选择使用红黑树。