TreeMap使用红黑树来存储节点信息,因此TreeMap分为三篇文章,这篇文章主要讲解红黑树的插入,TreeMap源码解析(二)主要讲解红黑树的删除,TreeMap源码解析(三)中主要讲解TreeMap的源码
红黑树概念及其基本性质
红黑树概念
红黑树是一种二叉查找树,它的每个节点上增加一个存储位用来表示节点的颜色信息,红黑树中节点的颜色只包含两种,一种是红色red,另一种是黑black
红黑树性质
因为红黑树是一种二叉查找树,因此下面描述二叉查找树的定义和性质
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
- 若左子树不空,则左子树上所有节点的值均小于或者等于它的根节点的值
- 若右子树不空,则右子树上所有节点的值均大于等于它的根节点的值
- 左右子树也分别为二叉查找树(二叉排序树)
每个结点的C(i)为该结点的层次数。最坏情况下,当先后插入的关键字有序时,构成的二叉排序树蜕变为单支树,树的深度为其平均查找长度(n+1)/2(和顺序查找相同),最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log 2 (n)成正比
在二叉查找树强制一般要求以外,对于任何有效的红黑树,其必须满足以下性质
- 性质1:节点是红色或者黑色
- 性质2:根节点是黑色
- 性质3:每个叶节点(NIL节点,空节点)是黑色
- 性质4:每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 性质5:从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。因为操作比如插入、删除和查找某个值的最坏情况时间都要求与树的高度成比例,这个在高度上的理论上限允许红黑树在最坏情况下都是高效的,而不同于普通的二叉查找树。
对于红黑树而言,其最重要的三个操作就是:左旋、右旋和着色
###树的旋转
当在对红黑树进行插入和删除等操作时,对树做了修改可能会破坏红黑树的性质。为了继续保持红黑树的性质,可以通过对结点进行重新着色,以及对树进行相关的旋转操作(左旋和右旋),即通过修改树中某些结点的颜色及指针结构,来达到对红黑树进行插入或删除结点等操作后继续保持它的性质或平衡的目的。
旋转又分为:左旋和右旋。通常左旋操作用于将一个向右倾斜的红色链旋转为向左
下面展示左旋操作:
下面展示右旋操作:
树的结构,我们设置:
(1)要插入的节点为N(new的首字母)
(2)父亲节点P(parent的首字母)
(3)祖父节点G(grandparents的首字母)
(4)叔节点U(uncle的首字母)
(5)兄弟节点S
红黑树插入和删除的分类情况
红黑树插入的分类
-
情况1:N的叔叔U是红色的
-
情况2:N的叔叔U是黑色的,且N是右孩子
- 情况3:N的叔叔U是黑色的,且N是左孩子
红黑树删除的分类
- 情况1: N的兄弟S是红色的。
- 情况2:N的兄弟S是黑色的,且S的2个孩子都是黑色的
- 情况3:N的兄弟S是黑色的,且S的左孩子是红色,S的右孩子是黑色
- 情况4:N的兄弟S是黑色的,且S右孩子是红色的
红黑树插入的具体情形解析
情况1:新节点N位于树的根上,没有父节点
void insert_case1(node n) {
if (n->parent == NULL)
n->color = BLACK;
else
insert_case2(n);
}
情况2:新节点的父节点P是黑色
void insert_case2(node n) {
if (n->parent->color == BLACK)
return; /* 树仍旧有效 */
else
insert_case3(n);
}
直接将新节点插入即可
情况3:父节点P、叔叔节点U,都为红色
这种情况违反了红黑树第四条性质:每个红色节点的两个子节点都是黑色的
解决的方法:将父亲P和叔叔U节点都涂黑,将祖父节点G涂红,上图红黑树变为下图
这种情况又导致了父子节点接连为红,违反了红黑树性质,插入修复的情况变为了情况4
情况4:当前节点N的父节点P是红色,叔叔节点U是黑色,当前节点是其父节点的右孩子
违反了红黑树性质第四条,解决的方法是:以当前节点的父节点为轴进行左旋,转换后的结果如下图
这种情况又导致了父子节点接连为红,违反了红黑树性质,插入修复的情况变为了情况5
情况5:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子
违反了红黑树性质4,解决方法:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋,转换后的结果如下图
红黑树插入的整个过程举例
将以下20个节点依次插入到红黑树中,插入后对应的修复情形如表格所示
1. 插入节点12
2. 插入节点1
该例子来自:https://blog.csdn.net/v_july_v/article/details/6284050
属于情况2:新节点的父节点是黑色
3. 插入节点9
属于情况1:新节点位于树的跟上,没有父节点;但是在此之前需要宣战
4. 插入节点2
属于情况3:父节点与叔叔节点都是红色;将父节点和叔叔节点都变为黑色
5. 插入节点0
属于情况2:新节点的父节点为黑色;直接插入节点即可
6. 插入节点11
属于情况2:直接插入元素即可
7. 插入元素7
属于情况3:父节点和叔叔节点都为红色,因此需要将父节点和叔叔节点变黑,将祖父节点变红
8. 插入节点19
属于情况2:新节点的父亲节点是黑色,直接插入新节点即可
9. 插入节点4
首先要让其满足二叉排序树性质,即先要旋转,旋转后,属于情况4;
情况4:父亲节点为红,叔叔节点为黑,当前节点是父节点的右孩子,则将当前节点变为黑
10. 插入节点15
属于情况2:新节点的父节点为黑色;即直接插入即可
11. 插入节点18
属于情况4:父节点为红,叔叔节点为黑,当前节点是父节点的左孩子
12. 插入节点5
属于情况3:父节点和叔叔节点都是红色;
解决:将父节点和叔叔节点都变黑色,将祖父节点变为红色;依次类推
13. 插入节点14
属于情况3:父节点和叔叔节点都是红色
14. 插入节点13
插入后违反了二叉排序树性质,因此需要先旋转(向右旋),然后调色
旋转后属于情况5:父节点为红,叔叔节点为黑,当前节点是父节点的左孩子
15. 插入节点10
属于情况2:新节点的父节点是黑色
16. 插入节点16
属于情况3:父节点和叔叔节点都是红色
17. 插入节点6
先右旋再左旋,旋转后属于情况4:父节点红,叔叔节点黑,当前节点是父亲节点的有孩子
18. 插入节点3
属于情况2:新节点的父节点是黑色
19. 插入节点8
属于情况3:父亲节点和叔叔节点都是红色
20. 插入节点17
先向左旋转,旋转后属于情况4
属于情况4:父节点为红,叔叔节点为黑,当前节点是父节点的右孩子
感谢
https://blog.csdn.net/v_JULY_v/article/details/6105630
https://blog.csdn.net/v_july_v/article/details/6284050