2-3-4树 红黑树其实是由2-3-4树演变来的,如果想要理解红黑树,可以先看看2-3-4树,其特点如下:
- 2-节点:包含 1 个元素的节点,有 2 个子节点;
- 3-节点:包含 2 个元素的节点,有 3 个子节点;
- 4-节点:包含 3 个元素的节点,有 4 个子节点;
- 元素始终保持排序顺序,整体上保持二叉查找树的性质,即父结点大于左子结点,小于右子结点;而且结点有多个元素时,每个元素必须大于它左边的和它的左子树中元素。
插入都是向最下面一层插入;
升元:将插入结点由 2-结点升级成 3-结点,或由 3-结点升级成 4-结点;
向 4-结点插入元素后,需要将中间元素提到父结点升元,原结点变成两个 2-结点,再把元素插入 2-结点中,如果父结点也是4-结点,则递归向上层升元,至到根结点后将树高加1;
- 插入 1 ,不用任何特殊处理
文章图片
- 插入 2,不用任何处理:
文章图片
- 插入 3 ,首先临时变为一个4节点,然后进行升元:
文章图片
文章图片
可以看到,中间元素进行2
升元,这时候树依然满足2-3-4树规则。
- 插入元素 4 ,不用进行任何处理:
文章图片
- 插入元素5,这时候需要进行升元,将元素4升元:
文章图片
- 插入元素6,不需要额外处理:
文章图片
- 插入元素7,这时候
5 6 7
是一个4节点,需要升元:
文章图片
- 插入元素8,不用进行任何处理:
文章图片
- 插入元素9,这时候,
7 8 9
需要进行升元:
文章图片
- 插入元素10,不需要额外处理:
文章图片
- 插入元素11,
9 10 11
需要进行升元:
文章图片
可以看到,通过2-3-4树的性质、升元操作,一颗2-3-4树,总是能够保持相对的平衡。
- 每个节点要么是红色,要么是黑色;
- 根节点永远是黑色的;
- 所有的叶节点都是是黑色的(注意这里说叶子节点其实是上图中的 NIL 节点);
- 每个红色节点的两个子节点一定都是黑色(不能有两个连续的红节点);
- 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点;
红黑树插入时对应2-3-4树规则如下:
- 新插入的结点颜色为红色,这样才可能不会对红黑树的高度产生影响。
- 2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
- 3-结点对应红黑树中的黑+红子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
- 4-结点对应红黑树中的红+黑+红子树,插入后将其修复成红色祖父+黑色父叔+红色孩子子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;
文章图片
2个节点的2-3-4树和红黑树表示如下:
文章图片
3个节点的2-3-4树和红黑树表示如下:
文章图片
可以很明显的看到,红黑树中,每个黑色节点和其红色子节点就是对应到了红黑树中一个节点,在2-3-4树中,就是黑色节点和其红色节点是在同一层的。
红黑树中插入节点规则如下:新插入的结点颜色为红色,这样才可能不会对红黑树的高度产生影响。
2-结点对应红黑树中的单个黑色结点,插入时直接成功(对应 2-结点升元)。
3-结点对应红黑树中的黑+红子树,插入后将其修复成 红+黑+红 子树(对应 3-结点升元);
4-结点对应红黑树中的红+黑+红子树,插入后将其修复成红色祖父+黑色父叔+红色孩子子树,然后再把祖父结点当成新插入的红色结点递归向上层修复,直至修复成功或遇到 root 结点;
红黑树保证了最长路径不超过最短路径的两倍,没有AVL树那么严格的平衡,但是旋转少效率高,也是O(LgN)。AVL树追求的是极致平衡,当你插入一个元素时,旋转的次数不能预估,当插入、删除特别频繁时,树就会不停地旋转,严重影响效率。
红黑树插入时,如果父节点是黑色,那么是不需要任何处理的,这时候实际上对应2-3-4树就是节点是一个2节点,直接加入即可
其它几种状况处理:
当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。
-
- 将“父节点”设为黑色。
-
- 将“叔叔节点”设为黑色。
-
- 将“祖父节点”设为“红色”。
-
- 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
这种情况红黑树如下,这时候我们要插入4:
文章图片
对应到2-3-4树就是:
文章图片
这时候就需要对当前节点进行升元:
文章图片
对应到红黑树如下:
- 将“祖父节点”设为“当前节点”(红色节点);即,之后继续对“当前节点”进行操作。
文章图片
这种情况下不需要旋转,只需要变色即可
当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的左子节点,同时当前节点也是父节点的左子节点,当前节点与父节点都是左节点
这时候也就是LL型,
比如 3-2,这时候插入 1,变成 3-2-1,
文章图片
文章图片
文章图片
对应红黑树为:
文章图片
而操作过程如下:
(1)将插入节点父节点变色
(2)以插入节点父节点为轴旋转
文章图片
当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的左子节点,同时当前节点是父节点的右子节点,
这就是所谓的LR型,这时候,对应2-3-4树如下:
文章图片
对应红黑树如下:
文章图片
这时候我们要恢复到标准的2-3-4结构:
文章图片
操作思路则是先将LR变为LL,然后在像LL一样操作即可。
(1)首先将 1 节点左旋,也就是插入节点的父节点左旋,变为LL 型
(2)按照LL型旋转操作
文章图片
当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的右子节点,同时当前节点也是父节点的右子节点,即插入节点与其父节点都是右子节点
:
这时候对应2-3-4树如下:
文章图片
文章图片
这时候还是不满足2-3-4树的性质,需要调整,调整如下:
(1) 父节点变为黑色,爷爷节点变为红色
(2)爷爷节点为轴,左旋:
文章图片
这样操作之后,能够满足2-3-4树和红黑树的特质。
当前节点的父节点是红色,叔叔节点不存在或叔叔节点为黑色,且父节点是爷爷节点的右子节点,当前节点是父节点的左子节点
,
即所谓的RL型。
这时候对应2-3-4树如下:
文章图片
对应红黑树为:
文章图片
(1)当前节点父节点右旋,变为RR,然后按照RR操作:
(2) 父节点变为黑色,爷爷节点变为红色
(3)爷爷节点为轴,左旋:
文章图片
【数据结构-算法|数据结构之红黑树,2-3-4树,插入旋转调整】这就是原理性的指导,代码实现我们可以参考下java中TreeMap的实现:
private void fixAfterInsertion(Entry x) {
x.color = RED;
// 必须是父节点是红色节点才会调整,黑色节点不需要
while (x != null && x != root && x.parent.color == RED) {
// 父节点是爷爷节点的左子节点
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
Entry y = rightOf(parentOf(parentOf(x)));
// 叔叔节点是红色,直接变色,不需要旋转
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// 当前节点还是父节点的右子节点,LR型,旋转,变为LL型
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
// LL型处理,将父节点设置为黑色,爷爷节点设置为红色,然后父节点为轴右旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateRight(parentOf(parentOf(x)));
}
} else {
// 这时候父节点是爷爷节点的左子节点
Entry y = leftOf(parentOf(parentOf(x)));
// 叔叔节点是红色,不需要调整,直接变色即可
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
// 当前节点是父节点的左子节点,RL型,先旋转为RR型
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rotateRight(x);
}
// RR型,将父节点设置为黑色,爷爷节点设置为红色,以父节点为轴,左旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}private void rotateRight(Entry p) {
if (p != null) {
Entry l = p.left;
p.left = l.right;
if (l.right != null) l.right.parent = p;
l.parent = p.parent;
if (p.parent == null)
root = l;
else if (p.parent.right == p)
p.parent.right = l;
else p.parent.left = l;
l.right = p;
p.parent = l;
}
}
private void rotateLeft(Entry p) {
if (p != null) {
Entry r = p.right;
p.right = r.left;
if (r.left != null)
r.left.parent = p;
r.parent = p.parent;
if (p.parent == null)
root = r;
else if (p.parent.left == p)
p.parent.left = r;
else
p.parent.right = r;
r.left = p;
p.parent = r;
}
}
算法演示: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html
推荐阅读
- 人工智能|参赛指南 | 教育部白名单竞赛少年硅谷 AI算法竞技赛
- LeetCode刷题|高精度乘法的两种实现方式
- #|《大话数据结构》从零开始 —— 第三章(线性表之链式存储结构 (单链表、静态链表、双向链表、循环链表))
- 算法|2021谷歌年度AI技术总结 | Jeff Dean执笔万字展望人工智能的5大未来趋势!
- 数据结构|数据结构基本概念以及线性表的基本操作
- #|数据结构与算法(2-2)线性表之链式存储(单链表、静态链表、循环链表、双向循环链表)
- 课程设计实验报告|本科课程【数据结构与算法】实验2——单链表与双向循环链表的插入、删除操作(C++实现)
- 剑指offer编程题解法汇总|力扣解法汇总1601- 最多可达成的换楼请求数目
- 剑指offer编程题解法汇总|力扣解法汇总2016-增量元素之间的最大差值