简单说一下二叉搜索树与AVL树
要学红黑树,首先你必须学会二叉搜索树,也就是二叉查找树,如果不会的同学,可以去看我写过的文章里面有
AVL树
他就是一个平衡二叉搜索树,什么叫平衡呢,就是一棵树维持一个稳定的二叉状态,维持一个稳定的高度,高度稳定了,它的查找时间复杂度也就低了,那么AVL树就是一个利用左右子树高度之差小于等于1的这样一个平衡因子来维持的树的平衡,一旦左右子树高度之差大于1,那么树就是开始旋转。
下面来简单对比一下二叉搜索树和AVL树
说一下上面的avl树为什么要经历六次旋转
这是第一次左旋的左旋的情况,右右不平衡
再来看一下第二次左旋的情况插入5的情况
后面情况就不多展示了
这里多说一下AVL树:
首先二叉树里面每一个节点里面的高度是怎么决定的?
在二叉树中,结点的高度(Height of a Node)是从该结点到叶结点(Leaf Node)的最长路径上的边数(或结点数减一)
很明显上面的示例不是一棵avl树 ,出现了平衡因子为2的结点。
那么为了解决AVL树严格的旋转问题,红黑树因此由运而生。
红黑叔:也是平衡二叉树,只是他没有AVL树那么严格,它的平衡因子是通过每一条路径上黑色结点数目一致来决定的,也就是黑色结点高度一致决定的
他在插入数据的过程中,就不用那么多次的旋转来插入数据。
这里简单说一下B树与B+树的存储结构
B树
我们在学习的过程中,可能还会碰到另外一些平衡查找树,比如说2-3树,2-3-4树,这里简单提一下2-3树,2-3-4树类似
当然你可以只包含2节点,只包含3结点,但一般不那么来做,它好像就是一种特殊的3阶B树,其实也就是可以看成三阶B树,只要这棵树符合定义的话。
说回来继续说B树,下面我们来看一个B树的结构
B树中数据的插入过程
下面我换一种可视化来表示,下面我们再继续插
这里插入33,是插到双亲结点里面去了,树的高度没有增加,像我们在插入53的时候,没有双亲结点,就会新建一个双亲结点,简而言之树的高度会在增加一层。而且我们会发现,所有的插入都会先从叶子结点试图插入,空间不够,然后才从中间结点开始向上分裂。
总结:左小右大从叶插,空间不够中间分
B树中数据删除的过程(这里就只是说了个大致过程,删除过程具体情况具体考虑)
删除分为两步:1.找到这个结点,2.然后删除
删除又分为两种情况考虑:第一种是叶子结点删除;第二种是非叶子结点删除
非叶子结点删除
下面就一句话一句话的来翻译删除步骤:
1.当一个结点删除之后,我们要看它当前结点是否满足第一点是最小的min_key值,也就是ceil(m)/2的值。
除根节点之外,其他非叶子结点至少有Math.ceil(m/2) - 1个关键字
2.如果不满足最小的min,我们就要去找孩子借,借了之后判断是否构成一个b树
这里找孩子借可以理解为,先找孩子借了然后依旧满足左小右大的原则
3.如果不构成,孩子结点又去找父亲借,然后考虑是否合并,同时考虑删除结点是否需要合并,也就是父节点需不需要去合并,反正必须满足B树的规则
主打就是一个符合B树的规则
那么如果另外一种情况是删了之后还是满足B树的min,我们就要考虑是否构成左小右大,如果不构成,就从左右子结点当中最多的一组里面找一个最小值或者最大值放上去,满足左小右大嘛。
我们来看一个插入过程:m = 5,min= 2(这里min的意思就是每个结点至少有两个key值)
我们要删除18这样一个非叶子结点 ,删除之后,明显不满足最小的min
删除之后不满足,我们去左结点里面借了一个17放上去,这个时候子结点又不满足min
然后我子结点去借一个父亲结点的最小值13(这里典型的就是借完孩子借父亲),然后合并
很明显17这个结点还是不满足要求,因为结点满足最少的key等于2 ,这个时候考虑合并上面
高度减少,同时符合B树要求
非叶子结点删除其实总结下来就一句话:借完孩子借父亲,随时随地看合并
叶子结点的删除
第一种情况是:如果删除的结点的key > min,那么删除就不影响,直接删了就行。
第二种情况是:从父亲结点去借一个下来,然后看需不需要合并,如果需要合并(这里合并也并不一定是不满足min,也可能就是一个左小右大的分叉,合理性问题),就把子结点合并,然后如果父结点不满足min,就去考虑是否再去子结点借还是合并,如果两个情况都满足,那么合并如果能减少树的高度,我们直接考虑为合并
具体考虑如下:m = 5,这是一个5阶B树
我们上面要删除4这个叶子结点,那么很明显删了之后不满足min ,就要去父亲借,借一个最小的下来,然后合并,这就是合理性,1235都是小于6的,所以直接合并。
上面这种情况我们考虑6 10 13 18的合并,这样可以降低树的高度
但是也可以如下考虑
但是这种情况树的高度不会变
总结一下叶子结点的删除:父子相借,看合并
一个简单的删除实例:
m=5 ,min = 2
上面就是介绍了B树的在内存中的逻辑结构
插入与删除的总结
插入:其实在叶上插,然后叶子满了,不够,向上分裂
这样插入的好处:
这种插入方式的好处在于,每次插入都只涉及到一个结点,而不是整个树的重组。这降低了插入操作的复杂度,减少了IO操作的次数,从而提高了插入性能。
B树的插入操作是从根结点逐级向下查找到叶子结点的过程。在这个过程中,只有包含插入关键字的路径上的结点需要被修改。由于磁盘IO往往是按块(block)进行的,这样的局部性保证了修改操作只涉及到有限数量的块,而不是整个树的结构。
删除:其实就是去借,借孩子,借父亲,子结点合并,父结点合并,终止保持这棵树是B树的状态,符合B树的规则
这样删除的好处:
1.磁盘IO的优化
B树的删除操作也是逐级向下进行的,只涉及到包含被删除关键字的路径上的结点。
这减少了删除操作涉及的磁盘IO次数,从而提高了删除性能。
2.平衡性
B树的删除操作会保持树的平衡。删除关键字后,B树会根据一些规则进行调整,以保证树的高度相对较小,从而降低了查询操作的时间复杂度
3.有序性
B树的删除操作能够保持结点内关键字的有序性。即使在删除关键字后进行结点的合并等操作,B树仍会保持结点内关键字的有序性,这对于需要有序数据的查询操作是有益的。
B树存储数据的特点
B树就是所有结点都存储数据
B+树
树的设计原则也和B树一样,插入与删除思路一致
下面我们说一下B+树它优化后的两个点
1.它的内部结点只存储部分索引,用于导航
2.内部结点包括根结点都不存储任何data数据,也就是磁盘上的数据,这样子来说,对于同一个m阶的B树来说,B+树每个结点能存储更多的key,换句话说,在某种情况下,它的树没有B树那么高,检索次数也就越少,那么磁盘IO的次数也就越少
3.对于范围检索来说,B+树的优势相对明显的,当我们找到叶子结点的时候,需要按顺序查找一定的范围,只要在这个磁盘上进行双向链表操作即可,不用进行多余的磁盘io
下面看一下B+树的图:
B树与B+树在磁盘索引上的应用
先来说磁盘
数据全都放在磁盘上,磁盘通过转动(这个大概就是会以一个固定的大小速度进行转动)盘面会去不同的磁道当中的检索数据。
说一下扇区,也可以理解为磁盘块,它是磁盘上面最小的可读写的数据块,通常由硬件或者操作系统定义大小,典型的磁盘大小呢就是512字节,1kb,4kb,在现代的文件系统中,常见磁盘块大小通常为4kb (扇区 = 磁盘块 = 磁盘最小可读写数据块)
下面我们说一下页:页可以理解为一个或者多个扇区的集合。它的大小通常为1024个字节或者其整数倍。主存和磁盘是以页为单位进行交换数据,一般我们一次读取一页或者几页载入到内存当中
(页 = 扇区(一个或者多个) = )以页为单位进行数据交换
也就是说,我们一次可以读取很多个硬盘块的数据到内存里面
磁盘的预读原理:将一个结点大小设置为一个页(一个页就是1024个byte或者其整数倍),那么每个结点只需要一次io操作就可以完全载入数据
那么这里对比一下插入10亿个数据,每一个数据是1byte,B树每一个结点大小是4kb=4000byte,每次从内存中拉取一页的大小,B树大概需要多少层,二叉树大概又需要多少层
二叉树
二叉树10亿个数据的检索大约就需18层
B树,每一个结点是4000,也就是说能存4000个key ,假设把它全部存满,那么这个m就应该设置为4001,说的稍微严谨一点是4001,我们直接当成4000来看
后一层数据 = 前一层数据 * m阶层
第一层: 4000
第二层: 4000 * m(4001)
第三层: 4000 * m(4001) * m(4001)
三层数据量 = 4000 + 16000000 + 64000000000 = 64016004000(六百多亿的数据量)
看一下大概三层就能存六百多个亿的数据
所以B树只需要三层就能数据存完
B+树它相比于B树它的树会更低,因为他首先内部结点没有全部存储所有的索引,只有一部分用于导航定位,所以它的树不会比B树高,那么每次在磁盘的io就会更少,我们可以看一下磁盘的检索方式
这个是B+树的方式
这个是B树的检索方式
下面我们引入几个常见问题
1.描述一下MySQL里面的B+树的索引原理
2.Mysql为什么选择使用B+树作为索引而不是B树
1.从树的高度来说,B+树会从两个方面来降低树的高度,从而降低查找的对比次数,第一个是每一个结点不存储数据,那么就会多出来更多的key去存放索引,树会变低,io的次数就会减少,第二个方面就是它内部不会存储全部的索引结点,只会存储部分索引用于导航,因此树也会相对更低,减少io次数
2.B+树的叶子结点是一个有序的双向链表,当我们需要范围查询的时候,我们直接从叶子结点找就可以了,不用再从根去一个节点一个节点对比,当然遍历也会更加高效,直接遍历叶子结点就可以了,而B树呢则需要遍历整棵树
2-3-4树与红黑树的关系
有了上面的知识,去弄懂什么是234树就很容易了
这里为什么要说2-3-4树,是因为红黑树,其实就是由2-3-4树演变而来
而这里的2-3-4树我们就可以看成一个四阶B树,它的插入过程,就符合B树的插入过程,不停从下向上进行分裂与合并。这里就直接拿一个完整的2-3-4树进行讲解
2,3,4结点和裂变状态与红黑树的对应关系(重要记住)
因此我们根据等价关系,把上面的2-3-4树变成红黑树就是
所以整棵树的形态变成红黑树就是下面的样子
当然根据3结点的左斜右斜还有不同形态,我就不多说了
红黑树的定义
怎么去理解这几个定义呢?
我们还是结合2-3-4树变红黑树的变换原则来看
不管是2结点,3结点,还是4结点,他们变成红黑树之后,上面的结点都是黑色,所以根结点肯定是黑的
那为什么一个红色结点下面两个子结点一定是黑的呢
那我们把这棵树拉回成一个2-3-4树的样子
我们看7这个结点,如果下面的子孩子结的是二节点,那么啥也不说了,直接就变成黑色结点
那么我们看一下如果下面接的是三结点的情况
那如果下面结的是四结点的情况呢
所以一个红色结点下面肯定会接两个黑色结点
那为什么从某个结点开始到叶子结点上面,每条路径都包含相同数目的黑色结点呢
还是去看变成红黑树的2-3-4树的图
很明显每一层的每一个结点都包含了一个黑色结点 ,所以不管从那一个结点开始它的黑色结点都是一样的
红黑树内部结点分叉情况与整棵树如果一直往一边插为什么不会造成一边树结点特别高的情况
下面是模拟了一下从10插到3整个过程
1.从10插到6
2.然后从5插到4
3.最后插3
很明显我们看到,整棵树在插入过程中,是在不停地变色调整的,所以,不会造成一边偏高的情况
判断下面的结点哪一个是红黑树,哪一个不是红黑树
先来看第一棵树是不是红黑树
结点分析:
再来看第二棵树是不是红黑树
树的分析
很明显这棵树就是红黑树
红黑树的理论研究
我们重点是放在黑色结点上面。红黑树的设计目标之一就是保持黑色高度的平衡,从而确保整棵树的相对平衡。
红黑树的红色节点通过在路径中交替出现,帮助保持了平衡。但在实际分析中,为了简化,通常将注意力集中在黑色高度上
问题:一棵有n个内部结点的红黑树它的高度最多为多少?
先来看一张关于树的图
上面总结归纳两点:
第一点:
第二点:
于是这个问题就得到解决
因此,对于红黑树来说,它的插入,删除,查找,时间复杂度都会在O(logn)的时间复杂度里面,相对于普通的二叉搜索树来说,普通的二叉搜索树最坏的情况下,会退化成一张单链表,因此这个时候的时间复杂度就是O(n)
红黑树的左旋与右旋(重要)
下面先来说左旋:
再来看一下右旋:
通过对比发现,左旋与右旋互为可逆操作。
来一句口诀
看分叉:
左旋看右边,右边看左边,左有放右边
右旋看左边,左边看右边,右有放左边
下面我们来完成左旋与右旋的代码
再说左旋与右旋之前,我们先来看一下红黑树的结点设计
static class RBNode<K extends Comparable<K>, V> {
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
//初始化一个根结点用
public RBNode(K key, V value, RBNode parent) {
this.parent = parent;
this.color = BLACK;
this.key = key;
this.value = value;
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode() {
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
然后分析一下左旋
上面如何去记呢,就是说p->parent= r一定是最后改的,这就是第六个指针,然后第一个指针就改p->right = pr->left(也就是说把分叉给连上呢),这个p->right(pr)这个时候就改了,对吗,因为后面还要用户p->right去她的left还有它的parent,所以我们这里必须要备份 。
下面代码实现
package com.pxx.tree.rbtree;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = false;
private static final boolean BLACK = true;
private RBNode<K, Object> root;//表示根结点
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
//左旋
/***
* p
* / \
*pl pr
* / \
* rl rr
* @param p
*/
private void leftRotate(RBNode p) {
if (p != null) {
//让p的右边指向r的左边
RBNode r = p.right;//先把r这个结点备份出来,因为等会要改变
p.right = r.left;
//因为pr左边可能没有,有的话,还要让rl.parent指向p
if (r.left != null) {
r.left.parent = p;
}
//现在要改变根结点的父结点的
r.parent = p.parent;
//下面我们要去改变p之前parent的子结点
//之前是p嘛,现在要改成pr
//还要考虑的是p这个结点之前是不是根结点
//这种情况如果是根结点
if (p.parent == null) {
root = r;//那么r上去就直接是根结点
} else if (p.parent.left == p) { //p是左孩子,要改成r是左孩子
p.parent.left = r;
} else {
p.parent.right = r;//那么p就是右孩子
}
//现在处理r的左边,也就是旋转之后左边子结点的处理
r.left = p;
p.parent = r;
}
}
static class RBNode<K extends Comparable<K>, V> {
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
//初始化一个根结点用
public RBNode(K key, V value, RBNode parent) {
this.parent = parent;
this.color = BLACK;
this.key = key;
this.value = value;
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode() {
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}
下面就是右旋,右旋就是把上面left变成right就可以了,因为左旋与右旋是不是就是反向操作呢
//右旋就是把上面左改成右,右改成左
private void rightRotate(RBNode p) {
if (p != null) {
RBNode l = p.left;//右旋看左边
p.left = l.right;//左边看右边,这个l.right可能等于null,没有关系
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;//最后改p.parent,前面要依靠他
}
}
红黑树结点的插入(重点理解)
首先插入的叶子必须是红色结点
下面我们讲解2-3-4树插入情况与红黑树的对应关系
1.1个结点,不合并
2. 新增一个结点,与2结点合并,直接合并,变成了三结点
这种红黑树对应的情况就是:新增一个红色结点+黑色接地那 = 上黑下红的形态(不调整)
3.新增了一个节点与三结点合并,就变成了四结点
红黑树:新增了一个红色结点 + 上黑下红 = 正确形态是中间是黑色,两边是红色
4.新增一个结点,与四结点进行合并,就要分裂,原来的的四节点中间升级为父节点,新增元素与剩下其中一个合并
红黑树呢就是:新增红色+爷爷黑色,父亲与叔叔是红色 = 爷爷变红(如果爷爷是根,变黑),父亲与叔叔变黑
然后整个红黑树插入,也是来依次比较值,然后在叶结点也就是合适的位置进行插入。其实整个插入的过程非常顺畅,难点在于修正的过程
下面根据上面关系,我们完成代码操作,代码内部有详细的说明注释,请仔细分析
package com.pxx.tree.rbtree;
public class RBTree5<K extends Comparable<K>, V> {
private static final boolean RED = false;
private static final boolean BLACK = true;
//键就是K,值不知道什么类型就用object替代
private RBNode<K, Object> root;
public RBNode getRoot() {
return root;
}
//左旋
private void leftRotate(RBNode p) {
if (p != null) {
RBNode r = p.right;
//从某种程度上来说,r这个结点是有可能为null
//先把孩子结点搭上
if (r != null) {
p.right = r.left;
if (r.left != null) {
r.left.parent = p;
}
}
//再来搭中间
r.left = p;
//主要是p的parent还不能改,因为还要搭r的双亲
//那么就先来改r的双亲
r.parent = p.parent;
//现在一个问题是p的左边指向它还是右边指向它
//就看指向p是左边还是右边,包括p这个结点是不是根结点
//根结点就等于null
if (p.parent == null) {
root = r;
} else if (p.parent.left == p) {
p.parent.left = r;
} else {
p.parent.right = r;
}
//最后改p的parent
p.parent = r;
}
}
//右旋
private void rightRotate(RBNode p) {
if (p != null) {
RBNode l = p.left;
//从某种程度上来说,r这个结点是有可能为null
//先把孩子结点搭上
if (l != null) {
p.left = l.right;
if (l.left != null) {
l.right.parent = p;
}
}
//再来搭中间
l.right = p;
//主要是p的parent还不能改,因为还要搭r的双亲
//那么就先来改r的双亲
l.parent = p.parent;
//现在一个问题是p的左边指向它还是右边指向它
//就看指向p是左边还是右边,包括p这个结点是不是根结点
//根结点就等于null
if (p.parent == null) {
root = l;
} else if (p.parent.left == p) {
p.parent.left = l;
} else {
p.parent.right = l;
}
//最后改p的parent
p.parent = l;
}
}
//获取父节点的方法
private RBNode parentOf(RBNode node) {
return node != null ? node.parent : null;
}
//获取结点左右孩子
private RBNode leftOf(RBNode node) {
return node != null ? node.left : null;
}
private RBNode rightOf(RBNode node) {
return node != null ? node.right : null;
}
private boolean colorOf(RBNode node) {
return node == null ? BLACK : node.color;
}
//对颜色进行设置
private void setColor(RBNode node, boolean color) {
if (node != null) {
node.color = color;
}
}
//开始向红黑树添加数据
public void put(K key,V value) {
RBNode<K, Object> t = this.root;
if (t == null) {
//这里直接设置根结点
root = new RBNode<>(key,value == null ? key : value, null);
}
//这两个声明在循环外面
//后面会用这两个数值去连接结点(最后的位置)和判断走向
RBNode parent;
int cmp;
if (key == null) {
throw new NullPointerException();
}
//下面去寻找插入位置
do {
parent = t;//这个就是当前插入结点的父亲,先初始化交给根结点
//这里就是compareTo这里面接收一个K泛型
//前面设置了t这个对象的接收类型 RBNode<K, Object>
//到时候会根据传入对象进行一个实际的数据类型转换
//key - t.key
cmp = key.compareTo(t.key);
if (cmp < 0) {
//t.key了,树里面结点大了就往左边走
t = t.left;
} else if (cmp > 0) {
t = t.right;
} else {
//这里把值替换了就行
t.setValue(value == null ? key : value);
return ;
}
} while (t != null);
//上面parent会停留在我们想要的结点上面
//我们来创建一个新结点
RBNode<K, Object> e = new RBNode<>(key, value == null ? key : value, parent);
//怎么知道是parent的left还是right
//是不是看cmp
//cmp > 0 说明传入的结点大走右边
if (cmp < 0) {
parent.left = e;
} else {
parent.right = e;
}
//核心问题是修正
//保持红黑树黑色结点平衡的问题
fixAfterPut(e);
}
//开始修正
private void fixAfterPut(RBNode x) {
//修正结点是红色
x.color = RED;//新建一个结点默认是黑色,我们要变成红色
//考虑一下结点什么时候需要调整
/*
* 情况1:新结点进来,与3结点合并,需要调整变色,最终的调整状态是上黑下红
* 这里面又分为两种小情况(非笔直的左左与右右):1.调整左左(右旋) 2.调整右右(左旋)
*/
/*
* 情况2:新结点进来,与4结点合并,需要进行变色,最终调整的状态是
* 原来的三结点变成上红下黑,然后递归到爷爷结点又按照上述思考进行判断
*
*
* 总结: 情况2是直接包含在情况1里面完成调整的
* */
//什么情况下要走进while循环进行调整:当父亲结点为红色的时候就需要循环调整
while (x != null && x != root && x.parent.color == RED) {
//直接来判断左左旋或者右右旋,上面情况2是嵌套在这个情况里面的
//左左斜 x父亲->父亲->左边
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//此时我们就需要去看一下情况2了,判断是不是新结点与四结点合并
//如何为情况2,其实很简单就是,叔叔为RED色就是情况2
//换句话说,就是看看叔叔到底存在不存在
//叔叔就是爷爷的右边
RBNode uncle = rightOf(parentOf(parentOf(x)));
if (colorOf(uncle) == RED) {
//针对于情况2这种情况,只需要调整颜色,不需要进行旋转
//颜色调整固定步骤
//父亲变黑,叔叔变黑,爷爷变红
setColor(parentOf(x), BLACK);
setColor(uncle, BLACK);
setColor(parentOf(parentOf(x)), RED);//color是一个布尔类型的数据
//在情况4这一步我们需要进行递归调整,情况3不用考虑递归
//直接交给爷爷
x = parentOf(parentOf(x));
} else {
//这个位置才正式开始调整左左,上面处理了情况2
//左左斜里面出现了左右不笔直的情况
//左右不笔直的情况是:1.左左,拿着父亲左旋 2.右右,拿着父亲右旋
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
leftRotate(x);//左旋
}
//不管调整不调整,走相同的改变颜色的代码
//父亲变黑,爷爷变红,拉着爷爷右旋
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rightRotate(parentOf(parentOf(x)));
}
/******处理右右斜的情况******************/
} else {
//右右斜这种情况好处理,左左里面左变右,右变左
RBNode uncle = leftOf(parentOf(parentOf(x)));
if (colorOf(uncle) == RED) {
setColor(parentOf(x), BLACK);
setColor(uncle, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
if (x == leftOf(parentOf(x))) {
x = parentOf(x);
rightRotate(x);
}
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
leftRotate(parentOf(parentOf(x)));
}
}/*****右右斜判断完毕*****/
}
root.color = BLACK;//根结点一定调整为黑色
}
//先来搭一个红黑树的内部结点
//静态内部类,避免内存搭多个结点内存
static class RBNode<K extends Comparable<K>, V> {
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
//生成构造方法
//初始化一个根节点,我们只要颜色,键值对
public RBNode(K key, V value , RBNode parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
//初始化一个结点
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode() {};
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}
红黑树的删除操作(重点理解)
这个可以用二叉搜索树的删除来看
1.删除叶子结点,直接删除
2.删除结点有一个子结点,那么就用子结点来替代
3.如果删除结点有两个子结点,那么就要找到直接前驱或者直接后继结点来替代(把这个替代结点的k与v放到删除上面去,然后把k与v都给放空)
上面不太懂的具体可以看我写的文章二叉搜索树的一个删除操作
一张图教你如何找前驱与后继
对于后继,直接left变right,right变left就行
拿着这个图来进行思考啊,下面代码看不懂,就配合着这个图来看
/**
* 找到指定结点的前驱结点,小于node结点的最大值
* @param node
* @return
*/
private RBNode predecessor(RBNode node) {
if (node == null) {
return null;
} else if (node.left != null) {//找到当前结点的左边,然后干到右边的尽头
RBNode p = node.left;
//直接干到右边
while (p.right != null) {
p = p.right;
}
//上面跳出了,直接返回p
return p;
} else {
//这个情况找前驱,其实我们不会进来
//因为这个相当于就是左边没有子结点,你怎么往下找前驱啊
//这个结点也不用找前驱了,直接右边拉上来接上就行了或者直接删除(叶子结点)
//这个情况也分为左右结点,左边的就要一直往上,直到right = p结束
RBNode p = node.parent;
//考虑为右叶子结点找前驱
if (p != null && p.right == node) {
return p;//直接就是p
} else {
//考虑为左叶子结点
RBNode cur = node;
while (p != null && cur == p.left) {//一旦p.right = cur,那么这个p就是前驱结点
cur = p;
p = p.parent;
}
return p;
}
}
}
//找后继结点
/**
* 找后继
* @param node
* @return
*/
private RBNode successors(RBNode node) {
//就是与上面完全相反的一种考虑方式
//找右子树左子树的最大值
if (node == null) {
return null;
} else if (node.right != null) {
//拿到右子树的第一个左子树,然后干到右边的尽头
RBNode p = node.right;
while (p.left != null) {//左边
p = p.left;
}
return p;
} else {//其实这条线根本不会走进来,子在写的时候也可以忽略掉
//考虑为叶子结点没有右子树,就一直往上直到parent.right != node
//然后上面是考虑为右结点的情况
//如果是左结点,直接就是parent
RBNode p = node.parent;
if (p!= null && p.left == node) {
return p;
} else {
//考虑为右边叶结点
RBNode cur = node;
while (p != null && cur == p.right) {
p = p.parent;
}
return p;
}
}
}
下面我们先把删除方法大体模板写上
完成得到这个结点的方法
下面我们去完成删除方法的框架
private void deleteNode(RBNode node) {
//node结点有两个孩子(2个孩子都有)
if (node.left != null && node.right != null) {
//找到后继结点继续替换
RBNode successor = successors(node);
//把键和值都给替换掉
node.key = successor.key;
node.value = successor.value;
//让node指向后继结点
node = successor;
}
//替换结点,看一下替换左边上去,还是替换右边上去
//计算两边都有,拿哪一个上去都是可以的
RBNode replacement = (node.left != null) ? node.left : node.right;
//先来这样判断一下
//一个节点删除之后,整棵树直接为空
if (replacement == null && node.parent == null) {
root = null;
} else if (replacement != null) {
//表明这有后继结点,左边或者右边,拿上去就行,非叶子结点
//那当前node结点的父亲 给 替换结点的父亲(我说了结点拿上去就可以了)
replacement.parent = node.parent;
//认了父亲之后,就要连儿子
if (node.parent == null) {
//这里就表示node为根结点
root = replacement;//随便拿上去一个节点就是根结点
} else if (node == node.parent.left) {//判断是从左连还是从右连
node.parent.left = replacement;
} else {
node.parent.right = replacement;
}
//开始把node进行脱钩,让gc进行销毁
//把左右都断掉
node.left = node.right = node.parent = null;//把相关的链全部断掉
//调整,删除的如果是黑色结点,必须调整
//红色不需要调整
if (node.color == BLACK) {
//需要调整
fixAfterRemove(replacement);
}
} else if (replacement == null) {
//表明就是叶子结点,直接删除
//叶子结点直接删除
//但是我们需要注意的是叶子结点如果先删除的话,是没有替换结点可以用来替换的
//所以,删除叶子结点我们先调整后删除
//先调整(s删除红色不调整)
if (node.color == BLACK && node.parent != null) {//等于null整棵树就没了
//fixAfterRemove(node)
}
//再删除
//不是根结点
if (node.parent != null) {//如果node是根接地那,左右为null,直接删了就是空树
//不然如果走了找后继结点的话,node.parent不可能会为空
if (node == node.parent.left) {
node.parent.left = null;//毕竟就是叶子结点
} else {
node.parent.right = null;
}
} else {
root = null;//整棵树直接挂掉
}
//把当前叶子结点给脱钩
node.parent = null;
}
}
再说删除调整之前, 先来说调整与删除的关系到底是怎样
这里说一个自己能处理的情况
上面根本不会进入调整的大while循环里面,检测当前结点为红色直接变黑。换句话说,只有叶子结点删除我们会进入大循环判断里面。
所以我们的代码一进来,就会直接判断删除的是左边的孩子与右边的孩子
我们考虑为左边的孩子就要去找右边的兄弟
下面讲解一下删除的调整分析
整体删除的位置
下面说一下几种删除的情况
删除的情况1:自己能搞定,对应的是3结点与4结点
但是删除某个结点我们要注意变色的问题,也就是调整的问题,比如我们删除6结点,其实就是找到直接后继,也就是7,然后给替换上去,把7这个位置给删除掉,此时7是一个黑色结点,所以我们需要对树进行修正,这个时候7和7.5这一个节点,就只剩下7.5这个key ,而7.5是一个2结点,对应的红黑树关系,2结点直接变黑修正
那如果我们删除的是8,它的直接后继就是9,9是一个红色结点,红色结点删除之后就不需要修正
删除情况2:自己搞不定,需要跟兄弟借,但是兄弟不借,就借助父亲来借,父亲下来,然后兄弟找一个人替代父亲当家
删除情况3:跟兄弟借,兄弟也没有
下面去写修正代码
下面说一下三结点与四节点能自己处理的删除情况,不需要旋转,也不需要去借
关于兄弟结点的调整问题
我们从2-3-4树来分析的删除,删除5,我们要去判断兄弟结点能不能借,也就是 7和7.5能不能借的问题,这个情况只会发生在叶子结点,所以,当我们变成红黑树的时候
我们就要去拿到兄弟结点,然后看兄弟结点的哪一个结点是黑色(黑色表明它是一个隐藏结点,也就表示没有),或者都是黑色,就表明没有结点
为什么是黑色,首先三结点的红黑树结构是上黑下红,也就是说,兄弟结点的孩子不管是在左边
还是在右边,都是红色。
话说回来,5对应的兄弟结点是7和7.5,那么看一下上面左边的红黑树,5对应的兄弟结点是8,很明显不对,所以我们需要如下调整
1.先变色,8变成黑,他组成的三结点6变红,那么这里也就是说
把8拿上去,6放下来,三结点是上黑下红嘛
其实就是整体用6来进行左旋
我们看一下下面的图
变成真正的兄弟结点之前,是下面这张图,7结点的子结点略有调整但是不重要
对应的代码如下
总结:
关于三结点如何借给兄弟结点key的问题
对应的代码
这个里面的colorOf()可以去判定叶子结点的下面子结点是否为空,返回BLACK肯定为空
因为这里对于三结点来说,孩子是为红色的,所以返回BLACK肯定为空
因为我们考虑把小的转上去,所以,只有可借接地那在左边的时候,也就是
右边为黑的情况下,我们才需要调整一下这个结点,这是我们向三结点借之前
的调整操作
此时
那么这个时候rnode就已经变了
rnode = rightOf(parentOf(x));//还是父亲的右孩子调整一下指针
如果我们去找四节点来借
这个时候,5结点考虑为: 把6.5借上去到父亲,父亲把6借给5结点,那么这里还是考虑旋转,这里采用两个方案
第一个方案旋转两次
先把6.5与7还有7.5转成一条线
再以6结点左旋,6.5转给父类,6转给子结点
第二个方案旋转一次,我一下借两个结点我6,6.5结点一起借,我不止把父亲的借给你,还把我自己的一个结点分给你
这里是直接拿到6进行旋转,左旋
在旋转之前,我们要先注意改变颜色
在旋转之前,注意颜色的设置
5 6 6.5 是中间黑两边红,8是黑色,7上去二节点变红,7.5是2结点直接变黑色
代码
三结点如果调整了也就是比如左边孩子转上去了之后,也就是右右一条线
会和四结点共用下面的操作
1.把兄弟变成父亲的颜色
2.把父亲变黑,兄弟结点的右边变黑(这两个结点以后就是兄弟结点)
3.拿着父亲结点左旋
4.字后把x指向root,表示调整结束
分析一下和兄弟借,但是兄弟也没有(这个时候兄弟情同手足,同时自损
下面是完整代码
package com.pxx.tree.rbtree;
public class RBTree<K extends Comparable<K>, V> {
private static final boolean RED = false;
private static final boolean BLACK = true;
private RBNode root;//表示根结点
public RBNode getRoot() {
return root;
}
public void setRoot(RBNode root) {
this.root = root;
}
//左旋
/***
* p
* / \
*pl pr
* / \
* rl rr
* @param p
*/
private void leftRotate(RBNode p) {
if (p != null) {
//让p的右边指向r的左边
RBNode r = p.right;//先把r这个结点备份出来,因为等会要改变
p.right = r.left;
//因为pr左边可能没有,有的话,还要让rl.parent指向p
if (r.left != null) {
r.left.parent = p;
}
//现在要改变根结点的父结点的
r.parent = p.parent;
//下面我们要去改变p之前parent的子结点
//之前是p嘛,现在要改成pr
//还要考虑的是p这个结点之前是不是根结点
//这种情况如果是根结点
if (p.parent == null) {
root = r;//那么r上去就直接是根结点
} else if (p.parent.left == p) { //p是左孩子,要改成r是左孩子
p.parent.left = r;
} else {
p.parent.right = r;//那么p就是右孩子
}
//现在处理r的左边,也就是旋转之后左边子结点的处理
r.left = p;
p.parent = r;
}
}
//右旋就是把上面左改成右,右改成左
private void rightRotate(RBNode p) {
if (p != null) {
RBNode l = p.left;//右旋看左边//备份左边
p.left = l.right;//左边看右边,这个l.right可能等于null,没有关系
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;//最后改p.parent,前面要依靠他
}
}
/**
* 找到指定结点的前驱结点,小于node结点的最大值
* @param node
* @return
*/
private RBNode predecessor(RBNode node) {
if (node == null) {
return null;
} else if (node.left != null) {//找到当前结点的左边,然后干到右边的尽头
RBNode p = node.left;
//直接干到右边
while (p.right != null) {
p = p.right;
}
//上面跳出了,直接返回p
return p;
} else {
//这个情况找前驱,其实我们不会进来
//因为这个相当于就是左边没有子结点,你怎么往下找前驱啊
//这个结点也不用找前驱了,直接右边拉上来接上就行了或者直接删除(叶子结点)
//这个情况也分为左右结点,左边的就要一直往上,直到right = p结束
RBNode p = node.parent;
//考虑为右叶子结点找前驱
if (p != null && p.right == node) {
return p;//直接就是p
} else {
//考虑为左叶子结点
RBNode cur = node;
while (p != null && cur == p.left) {//一旦p.right = cur,那么这个p就是前驱结点
cur = p;
p = p.parent;
}
return p;
}
}
}
//找后继结点
/**
* 找后继
* @param node
* @return
*/
private RBNode successors(RBNode node) {
//就是与上面完全相反的一种考虑方式
//找右子树左子树的最大值
if (node == null) {
return null;
} else if (node.right != null) {
//拿到右子树的第一个左子树,然后干到右边的尽头
RBNode p = node.right;
while (p.left != null) {//左边
p = p.left;
}
return p;
} else {//其实这条线根本不会走进来,子在写的时候也可以忽略掉
//考虑为叶子结点没有右子树,就一直往上直到parent.right != node
//然后上面是考虑为右结点的情况
//如果是左结点,直接就是parent
RBNode p = node.parent;
if (p!= null && p.left == node) {
return p;
} else {
//考虑为右边叶结点
RBNode cur = node;
while (p != null && cur == p.right) {
p = p.parent;
}
return p;
}
}
}
//获取父节点的方法
private RBNode parentOf(RBNode node) {
return node != null ? node.parent : null;
}
//获取结点左孩子的方法
private RBNode leftOf(RBNode node) {
return node != null ? node.left : null;
}
//获取结点右孩子的方法
private RBNode rightOf(RBNode node) {
return node != null ? node.right : null;
}
//获取颜色的方法
private boolean colorOf(RBNode node) {
return node == null ? BLACK : node.color;//为空,那默认就是黑色
}
//设置颜色的方法
private void setColor(RBNode node, boolean color) {
if (node != null) {
node.color = color;
}
}
public void put(K key, V value) {
RBNode t = this.root;//先拿到根结点
if (t == null) {
//这里不是RBNode<K,V>为了处理value为null情况。默认值为k,
//但是这个key是K类型,要兼容只能用Object类型
RBNode<K, Object> root= new RBNode<>(key, value == null ? key : value, null);
return;
}
//下面我们结点进来就要找插入的位置
//寻找插入位置
RBNode parent;//保留t的父亲
int cmp;//等会用来比较的值
if (key == null) {
throw new NullPointerException();
}
//沿着根节点寻找插入位置
do {
parent = t;
//首先判断当前对象与传入对象大小,此时决定了往左边走还是往右边走
//传入小,走左边,传入大,走右边
//这里比较this - 传入者
//> 0 表示传入的小-> right < 0 表示传入的大 走left(注意传入的是当前根结点)
cmp = key.compareTo((K)t.key);//调用k的实现的Comparable<k>比较器里面的方法,但是这里t.key它是一个
//K extends Comparable<K>也就是Comparable,所以强制类型转换一下
if (cmp < 0) {
t = t.left;
} else if (cmp > 0) {
t = t.right;
} else { //两个值相等的情况
//相等的key,传入的value会覆盖掉原来的value
t.setValue(value == null ? key : value);
return;//这里找到了就要退出
}
} while (t != null);//会让t一直往后面找
//上面跳出来之后,说明就是已经到了指定的位置上面
//因为你始终必须知道插入是在叶子结点上面进行的
//此时parent就是插入结点的父类,t讲就已经到了null的位置
//先来在堆上新创建一个结点
RBNode<K, Object> e = new RBNode<>(key, value == null ? key : value, parent);
//下面我们要判断是parent的左子树还是右子树
//最后跳出的cmp
if (cmp < 0) {
//根大,走左边
parent.left = e;
} else {
parent.right = e;
}
//开始修正(插入的这个e结点可能就是需要修正的)
//也可能不需要修正,我们在内部进行讨论
//其实只要情况4进来的时候才会走第一次的while循环,而一次递归就是走colorOf(y)判断爷爷是红色这条路
fixAfterPut(e);
}
//开始进行修正
private void fixAfterPut(RBNode x) {
x.color = RED;//变红,默认结点是黑色(进来变红色)
//本质上就是父节点是黑色就不需要调整,
//新节点进来与1结点合并,变为2结点不需要调整
//新节点进来与3结点合并,需要调整左左或者右右,左左与右右的操作其实就是相反的嘛
//看上面的第三种情况,调整右右,左旋,上面变黑,下面变红 调整左左,就右旋,上面变黑,下面变红
//还要指注意,我们调整,不能只调整一棵树的片段
//需要调整整棵树的部分
//所以下面必须用while循环进行一个递归(递归到根结点结束)
while (x != null && x != root && x.parent.color == RED) {
//1.调整情况3的左左,注意这个x是插入的结点
//x的父节点是爷爷左孩子(左3)(这个就是情况3的左左)
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//拿到叔叔,叔叔结点为红,那么爷爷变红,父亲变黑,叔叔变红,也就是情况四的四种情况
//第四种情况直接调整为正确情况,并且要把x交给爷爷,继续往上面判断
//然后如果在第四种把情况插入中出现第三种情况就走else的情况
RBNode 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));//以爷爷为子结点往上继续判断
//不用在x里面进行递归了,因为每次的else,就会把叶子结点部分变为有序的状态,这个
//状态不用递归,然后下一次进来肯定走colorOf,然后继续往上到根结点
} else {
//在第三种情况里面,还有一种左左是把不笔直的左左
//就是x结点可能在父亲的右边,可以对应情况3去看一下
//如果是情况3右右的情况,就要调整左边
if (x == rightOf(parentOf(x))) {
//其实就是要调整为笔直的左左
//根据x的parent进行左旋
x=parentOf(x);//利用父节点左旋
leftRotate(x);
}
//下面就是彻底的情况3的情况,父亲为红,插入为红,左左
//这种情况就是,下面的操作左左与右右都是一样的
//父亲变黑,爷爷变红,这是左左,考虑右旋(根据爷爷右旋)
setColor(parentOf(x), BLACK);//父亲变黑
setColor(parentOf(parentOf(x)), RED);//爷爷变红
//根据爷爷右旋,成为情况3里面正确的状态
rightRotate(parentOf(parentOf(x)));
}
//然后以
} else {//左左就判断完了
//第三种情况的右右
//右右的话,叔叔就是爷爷结点的左边
RBNode 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 {
//在第三种情况里面,还有一种左左是把不笔直的左左
//就是x结点可能在父亲的右边,可以对应情况3去看一下
//如果是情况3右右的情况,就要调整左边
if (x == leftOf(parentOf(x))) {//右右变为左边
//其实就是要调整为笔直的左左
//根据x的parent进行左旋
x=parentOf(x);//利用父节点右旋
rightRotate(x);
}
//下面就是彻底的情况3的情况,父亲为红,插入为红,左左
//这种情况就是,下面的操作左左与右右都是一样的
//父亲变黑,爷爷变红,这是左左,考虑右旋(根据爷爷右旋)
setColor(parentOf(x), BLACK);//父亲变黑
setColor(parentOf(parentOf(x)), RED);//爷爷变红
//根据爷爷右旋,成为情况3里面正确的状态
leftRotate(parentOf(parentOf(x)));//右右就是左旋
}
}//右右判断完毕
}//整体while循环
//最后结束了rooty一定要变黑
root.color = BLACK;
}
//删除操作(返回删除的value值)
public V remove(K key) {
//要删除值先拿到值
RBNode node = getNode(key);
if (node == null) {
return null;
}
V oldVlaue = (V)node.getValue();
//删除
deleteNode(node);
return oldVlaue;
}
private void deleteNode(RBNode node) {
//node结点有两个孩子(2个孩子都有)
if (node.left != null && node.right != null) {
//找到后继结点继续替换
RBNode successor = successors(node);
//把键和值都给替换掉
node.key = successor.key;
node.value = successor.value;
//让node指向后继结点
node = successor;
}
//替换结点,看一下替换左边上去,还是替换右边上去
//计算两边都有,拿哪一个上去都是可以的
RBNode replacement = (node.left != null) ? node.left : node.right;
//先来这样判断一下
//一个节点删除之后,整棵树直接为空
if (replacement == null && node.parent == null) {
root = null;
} else if (replacement != null) {
//表明这有后继结点,左边或者右边,拿上去就行,非叶子结点
//那当前node结点的父亲 给 替换结点的父亲(我说了结点拿上去就可以了)
replacement.parent = node.parent;
//认了父亲之后,就要连儿子
if (node.parent == null) {
//这里就表示node为根结点
root = replacement;//随便拿上去一个节点就是根结点
} else if (node == node.parent.left) {//判断是从左连还是从右连
node.parent.left = replacement;
} else {
node.parent.right = replacement;
}
//开始把node进行脱钩,让gc进行销毁
//把左右都断掉
node.left = node.right = node.parent = null;//把相关的链全部断掉
//调整,删除的如果是黑色结点,必须调整
//红色不需要调整
if (node.color == BLACK) {
//需要调整,替换结点一定是红色这里
fixAfterRemove(replacement);
}
} else if (replacement == null) {
//表明就是叶子结点,直接删除
//叶子结点直接删除
//但是我们需要注意的是叶子结点如果先删除的话,是没有替换结点可以用来替换的
//所以,删除叶子结点我们先调整后删除
//先调整(s删除红色不调整)
if (node.color == BLACK && node.parent != null) {//等于null整棵树就没了
//fixAfterRemove(node)
}
//再删除
//不是根结点
if (node.parent != null) {//如果node是根接地那,左右为null,直接删了就是空树
//不然如果走了找后继结点的话,node.parent不可能会为空
if (node == node.parent.left) {
node.parent.left = null;//毕竟就是叶子结点
} else {
node.parent.right = null;
}
} else {
root = null;//整棵树直接挂掉
}
//把当前叶子结点给脱钩
node.parent = null;
}
}
//开始写删除调整的代码:
private void fixAfterRemove(RBNode x) {
//你想一下,自己搞不定的话
//那么也就是说只有一个节点
//也就是说,它是2结点的情况是自己搞不定的,删除红色,不调整
//所以这个结点进来调整肯定是黑色
//那么这里就包含了情况2与情况3
while (x != root && colorOf(x) == BLACK) {
//不管是有得借还是没得借,都要先去找兄弟结点(有借没借都通过2-3-4树考虑)
//兄弟结点我们又要看是左边的兄弟结点还是右边的兄弟结点
if (x == leftOf(parentOf(x))) {
//这里就是x是左孩子的情况
//这是左孩子,就找右孩子
RBNode rnode = rightOf(parentOf(x));
//判断此时兄弟结点是不是真正的兄弟结点
//比如5与8,这里等会用图说明
//红色的情况,就不是真正的兄弟结点
//把rnode变黑,6变红
if (colorOf(rnode) == RED) {
setColor(rnode,BLACK);
setColor(parentOf(x), RED);//6变红
//沿着6开始左旋
leftRotate(parentOf(x));//就会变成正确的兄弟结点
//重新把兄弟结点变一下,真正的兄弟,这里是7
rnode = rightOf(parentOf(x));//旋转之后,x的父亲的右孩子就是我们的兄弟
}
//情况三,找兄弟借,兄弟没得借
if (colorOf(leftOf(rnode)) == BLACK && colorOf(rightOf(rnode)) == BLACK) {
//把兄弟变成红色,自损
setColor(rnode, RED);
//然后递归自己父类,父类只要是红色,就变黑就平衡了
x = parentOf(x);
} else {
//这里就是情况2,找兄弟借,兄弟有得借
//里面借的话又分成两种小情况:1.兄弟结点本来是3结点借或者4结点借的情况
//第一种是三结点的情况(比如7和7.5),这个结点只会连一个孩子的情况左为空或者右为空
//这个都是在叶子结点来考虑,三结点,就是上黑下红,如果左边或者右边为黑,则表示为空
if (colorOf(rightOf(rnode)) == BLACK) {//6.5左边 7在上面,三结点的情况
//兄弟结点的左孩子,也就是6.5
setColor(leftOf(rnode), BLACK);
//rnode变红
setColor(rnode,RED);
//然后把6.5和7进行右旋,目的是为了给到父亲
//父亲在借给它的左孩子
rightRotate(rnode);
rnode = rightOf(parentOf(x));//还是父亲的右孩子
}
//上面是三结点才进去,四结点直接走下面
//把7变红,她要去上去与8黑成为二节点(rnode变红)
setColor(rnode,colorOf(parentOf(x)));//其实就是把7变成父亲的颜色
//这里父亲的颜色不一定是红色,也可能是黑色
//在把父亲的颜色变黑,也就是6颜色,两边是红色5和6.5
setColor(parentOf(x), BLACK);
//下面要把兄弟的右孩子变成黑色,毕竟借出去了之后,它是一个二节点,直接变黑
setColor(rightOf(rnode), BLACK);
//5是要删除的所以不用管
//上面变完颜色之后,沿着6左旋,也就是x的父亲
leftRotate(parentOf(x));
//注意最后让x指向root
x= root;//表明这次判定结束
}
} else {
//左右相反,左改右,右改左
//这里就是x是左孩子的情况
//这是左孩子,就找右孩子
RBNode lnode = leftOf(parentOf(x));
//判断此时兄弟结点是不是真正的兄弟结点
//比如5与8,这里等会用图说明
//红色的情况,就不是真正的兄弟结点
//把rnode变黑,6变红
if (colorOf(lnode) == RED) {
setColor(lnode,BLACK);
setColor(parentOf(x), RED);//6变红
//沿着6开始左旋
rightRotate(parentOf(x));//就会变成正确的兄弟结点
//重新把兄弟结点变一下,真正的兄弟,这里是7
lnode = leftOf(parentOf(x));//旋转之后,x的父亲的右孩子就是我们的兄弟
}
//情况三,找兄弟借,兄弟没得借
if (colorOf(leftOf(lnode)) == BLACK && colorOf(rightOf(lnode)) == BLACK) {
//把兄弟变成红色,自损
setColor(lnode, RED);
//然后递归自己父类,父类只要是红色,就变黑就平衡了
x = parentOf(x);
} else {
//这里就是情况2,找兄弟借,兄弟有得借
//里面借的话又分成两种小情况:1.兄弟结点本来是3结点借或者4结点借的情况
//第一种是三结点的情况(比如7和7.5),这个结点只会连一个孩子的情况左为空或者右为空
//这个都是在叶子结点来考虑,三结点,就是上黑下红,如果左边或者右边为黑,则表示为空
if (colorOf(leftOf(lnode)) == BLACK) {//6.5左边 7在上面,三结点的情况
//兄弟结点的左孩子,也就是6.5
setColor(rightOf(lnode), BLACK);
//rnode变红
setColor(lnode,RED);
//然后把6.5和7进行右旋,目的是为了给到父亲
//父亲在借给它的左孩子
leftRotate(lnode);
lnode = leftOf(parentOf(x));//还是父亲的右孩子
}
//上面是三结点才进去,四结点直接走下面
//把7变红,她要去上去与8黑成为二节点(rnode变红)
setColor(lnode,colorOf(parentOf(x)));//其实就是把7变成父亲的颜色
//这里父亲的颜色不一定是红色,也可能是黑色
//在把父亲的颜色变黑,也就是6颜色,两边是红色5和6.5
setColor(parentOf(x), BLACK);
//下面要把兄弟的右孩子变成黑色,毕竟借出去了之后,它是一个二节点,直接变黑
setColor(leftOf(lnode), BLACK);
//5是要删除的所以不用管
//上面变完颜色之后,沿着6左旋,也就是x的父亲
rightRotate(parentOf(x));
//注意最后让x指向root
x= root;//表明这次判定结束
}
}
}
//情况1,自己能搞定
setColor(x, BLACK);//提点结点是红色,直接染黑,补偿删除的黑色
}
private RBNode getNode(K key) {
RBNode node = this.root;
//从根结点开始循环
while (node != null) {
int cmp = key.compareTo((K)node.key);
if (cmp < 0) {
node = node.left;
} else if (cmp > 0) {
node = node.right;
} else {
return node;
}
}
//没找到
return null;
}
static class RBNode<K extends Comparable<K>, V> {
private RBNode parent;
private RBNode left;
private RBNode right;
private boolean color;
private K key;
private V value;
//初始化一个根结点用
public RBNode(K key, V value, RBNode parent) {
this.parent = parent;
this.color = BLACK;
this.key = key;
this.value = value;
}
public RBNode(RBNode parent, RBNode left, RBNode right, boolean color, K key, V value) {
this.parent = parent;
this.left = left;
this.right = right;
this.color = color;
this.key = key;
this.value = value;
}
public RBNode() {
}
public RBNode getParent() {
return parent;
}
public void setParent(RBNode parent) {
this.parent = parent;
}
public RBNode getLeft() {
return left;
}
public void setLeft(RBNode left) {
this.left = left;
}
public RBNode getRight() {
return right;
}
public void setRight(RBNode right) {
this.right = right;
}
public boolean isColor() {
return color;
}
public void setColor(boolean color) {
this.color = color;
}
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
}
下面贴一个c语言代码,参考
#include <stdio.h>
#include <stdlib.h>
#define RED 0
#define BLACK 1
typedef int KEY_TYPE;
//定义红黑树结点
typedef struct _rbtree_node {
struct _rbtree_node *parent;
struct _rbtree_node *left;
struct _rbtree_node *right;
int color;
KEY_TYPE key;
void *value;
}rbtree_node;
//定义一个根结点
//null结点也是一个红黑树结点
//当指向了nil的时候,就表明是一个隐藏结点,也可以理解为空节点
typedef struct _rbtree {
rbtree_node *root;
rbtree_node *nil;
}rbtree;
//实现左旋
//加入我们要旋转的结点x,如果结点为根结点也就是x->parent == t->nil,那么y就是根结点
void rbtree_left_rotate(rbtree *t, rbtree_node *x) {
if (t != null && x->right != null) {
//左旋看右边
rbtree_node *y = x->right;
//右边看左边,有值放右边
//如果有值,那就先把线搭上去
x->right = y->left;
//有值的父亲一定要变成x
if (y->left != T->nil) {
y->left->parent = x;
}
//左边分叉就搭好了
//最后该x->parent;因为要用x->parent去指向y
//开始搭y的parent
y->parent = x->parent;
//还有一条线是y->left = x;
//放到最后搭x->parent的时候在搭
//先来搭y的父亲
if (x->parent == t->nil) {
t->root = y;//不用搭左边或者是右边
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
//最后搭y的左边和x的parent
y->left = x;
x->parent = y;
}
}
//实现右旋
void rbtree_right_rotate(rbtree *t, rbtree_node *x) {
if (t != null && x->left != null) {
//左旋看右边
rbtree_node *y = x->left;
//右边看左边,有值放右边
//如果有值,那就先把线搭上去
x->left = y->right;
//有值的父亲一定要变成x
if (y->right != T->nil) {
y->right->parent = x;
}
//左边分叉就搭好了
//最后该x->parent;因为要用x->parent去指向y
//开始搭y的parent
y->parent = x->parent;
//还有一条线是y->left = x;
//放到最后搭x->parent的时候在搭
//先来搭y的父亲
if (x->parent == t->nil) {
t->root = y;//不用搭左边或者是右边
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
//最后搭y的左边和x的parent
y->right = x;
x->parent = y;
}
}
//这个不像java一样直接给key 和 value
//其实也可以给key和value,那么就在内部初始化结点就好了
void rbtree_insert(rbtree *t, rbtree_node *z)
{
//还是那句话,所有的插入都是在叶子结点上操作的
rbtree_node *y = t->nil;//保留一个结点,指向插入结点的父类,先指向为空节点
rbtree_node *x = t->root;//从根结点开始移动
//不等于最后一个结点的时候,就一直循环
while (x != t->nil) {
y = x;//每次让y指向x的父节点
//然后来判断key的左移动还是右移动
//jav这里是让key去实现了一个比较器
//然后来参与比较
if (z->key < x->key) {
x = x->left;
} else if (z->key > x->key) {
x = x->right;
} else {
//这里的操作可以把value给替换掉
x->value = z->value;//直接替换x的地址指向
return ;
}
}
//上面y就会循环到插入结点的父节点
//这里判定就是整棵树里面有没有结点
//如果没有结点x= t->root = t->nil
//这个时候y就等于t->nil
z->parent = y;//顶部就是空的,也就是没有
if (y == t->nil) {
t->root = z;
} else if (z->key < y->key) {
y->left = z;
} else {
y->right = z;
}
//最后初始化z这个结点
z->left = t->nil;
z->right = t->nil;
rbtree_insert_fixup(t, z);//修正的是插入这个结点
}
//写几个辅助函数来用
//找父亲的函数
rbtree_node* parent_of(rbtree *t, rbtree_node *x)
{
if (t != NULL && x != t->nil)
{
return x->parent;
}
return NULL;
}
//找左结点的函数
rbtree_node* left_of(rbtree *t, rbtree_node *x)
{
if (t != NULL && x != t->nil)
{
return x->left;
}
return NULL;
}
//找右结点
rbtree_node* right_of(rbtree *t, rbtree_node *x)
{
if (t != NULL && x != t->nil)
{
return x->right;
}
return NULL;
}
//设置颜色
void setColor(rbtree *t, rbtree_node *x, int color)
{
if (t != NULL && x != t->nil)
{
x->color = color;//让x的颜色变为color
}
return ;
}
//插入的修正
void rbtree_insert_fixup(rbtree *t, rbtree_node *x)
{
//插入的这个结点先变红
x->xolor = RED;//每个结点进来是红色
//这个过程是一直往上面递归循环调整,直到走到根结点
//父亲为红色才需要调整
//只要变成三结点的时候才会进来
//如果是四结点那么交给爷爷进行往上递归,如果是根结点就变黑
while (x != t->nil && x != t->root && x->parent.color = RED)
{
//判定左左斜还是右右斜这两个是相对操作
//这种情况可以表示为情况3会先出现,然后情况4内部改变然后递归
//情况4是在情况3的基础上面出现
//换句话说第三种情况一直会出现,然后内部在判定处理还是不处理第四种情况
//我们写左左斜的逻辑
//如果是右右斜把左左斜左全部改成右就可以了
if (parent_of(x) == left_of(parentOf(parentOf(x))))
{
//判断是不是第四种情况
//如果是第四种情况,那么进来调整,然后向上递归到根结点
//判定第四种情况就是叔叔是红色的情况
//既然是左左斜就来判定右边的叔叔
rbtree_node *y = right_of(parent_of(parent_of(x)));
if (y->color == RED)
{
//叔叔是红色
//这里就是父亲与叔叔变黑,爷爷变红
//然后x交给爷爷继续递归
setColor(t, parent_of(x), BLACK);//父亲变黑
setColor(t, y, BLACK);
setColor(t, parent_of(parent_of(x)), RED);
x = parent_of(parent_of(x));
}
else
{
//这种情况就是情况3,不需要走情况4,也不用递归
//在情况3里面也还是有小情况出现
//左左斜出现右小斜
//处理方法就是,先变成直线,
//右小斜就是以父亲左旋
if (x == right_of(parent_of(x)))
{
//拿着父亲左旋
x = parent_of(x);
rbtree_left_rotate(x);
}
//上面就是旋转成了一条笔直的线
//然后开始进行变色
//父亲变黑,爷爷变红
//左左斜就是根绝爷爷右旋
setColor(t, parent_of(x), BLACK);
setColor(t, parent_of(parent_of(x)), RED);
rbtree_right_rotate(t, parent_of(parent_of(x)));
}
}
else
{
//这里就处理右右斜的情况
//判断是不是第四种情况
//如果是第四种情况,那么进来调整,然后向上递归到根结点
//判定第四种情况就是叔叔是红色的情况
//既然是左左斜就来判定右边的叔叔
rbtree_node *y = left_of(parent_of(parent_of(x)));
if (y->color == RED)
{
//叔叔是红色
//这里就是父亲与叔叔变黑,爷爷变红
//然后x交给爷爷继续递归
setColor(t, parent_of(x), BLACK);//父亲变黑
setColor(t, y, BLACK);
setColor(t, parent_of(parent_of(x)), RED);
x = parent_of(parent_of(x));
}
else
{
//这种情况就是情况3,不需要走情况4,也不用递归
//在情况3里面也还是有小情况出现
//左左斜出现右小斜
//处理方法就是,先变成直线,
//右小斜就是以父亲左旋
if (x == left_of(parent_of(x)))
{
//拿着父亲左旋
x = parent_of(x);
rbtree_right_rotate(x);
}
//上面就是旋转成了一条笔直的线
//然后开始进行变色
//父亲变黑,爷爷变红
//左左斜就是根绝爷爷右旋
setColor(t, parent_of(x), BLACK);
setColor(t, parent_of(parent_of(x)), RED);
rbtree_left_rotate(t, parent_of(parent_of(x)));
}
}
}
//掏出循环之后
//根结点变黑
t->root->color = BLACK;
}
//做删除之前,我们先来做两个方法,找前驱或者后继
//因为删除等会要用到这个
//找前驱结点,找左边,然后干到左边结点的最右边,左边不为NULL的情况
rbtree_node *predecessor(rbtree *t, rbtree_node *node)
{
if (node == t->nil)
{
return NULL;
}
else if (node->left != t->nil)
{
//左边,干到右边的最大值
rbtree_node *p = node->left;
while (p->right != t->nil)
{
p = p->right;
}
return p;
}
else
{
//左边如果没有躯干下去
//考虑为左右叶子结点找前驱
//先把父亲拿到
rbtree_node *p = node->parent;
//考虑为右叶子结点,直接就是它的父亲
//这里最先插入的时候root的parent就是指向了t->nil
if (p != t->nil && p->right == node)
{
return p;
}
else
{
//考虑为左叶子结点找前驱
//它的父亲的左边一直指向它,直到不是左边指向它
rbtree_node *cur = node;
while (p != t->nil && cur = p->left)
{
cur = p;
p = p->parent;
}
//一旦跳出来就说明没有前驱或者找到了
return p;
}
}
}
//找后继
//做删除之前,我们先来做两个方法,找前驱或者后继
//因为删除等会要用到这个
//找前驱结点,找左边,然后干到左边结点的最右边,左边不为NULL的情况
rbtree_node *successors(rbtree *t, rbtree_node *node)
{
if (node == t->nil)
{
return NULL;
}
else if (node->right != t->nil)
{
//左边,干到右边的最大值
rbtree_node *p = node->right;
while (p->left != t->nil)
{
p = p->left;
}
return p;
}
else
{
//左边如果没有躯干下去
//考虑为左右叶子结点找前驱
//先把父亲拿到
rbtree_node *p = node->parent;
//考虑为右叶子结点,直接就是它的父亲
//这里最先插入的时候root的parent就是指向了t->nil
if (p != t->nil && p->left == node)
{
return p;
}
else
{
//考虑为左叶子结点找前驱
//它的父亲的左边一直指向它,直到不是左边指向它
rbtree_node *cur = node;
while (p != t->nil && cur = p->right)
{
cur = p;
p = p->parent;
}
//一旦跳出来就说明没有前驱或者找到了
return p;
}
}
}
//在删除的时候,我们必须拿到这个结点
//如果它传入的是一个key
//我们就要去找这个key的位置
//如果传进来的是一个node ,那么根本就不需要去找位置
rbtree_node *get_node(rbtree *t, key_TYPE key)
{
if (t != NULL)
{
//让key去比较node
//把一个node结点作为根来进行操作
rbtree_node *node = t->root;
//做一个循环,循环可以判定到node != null
while (node != t->nil)
{
int cmp = key - (node->key);
if (cmp > 0)
{
node = node->right;
}
else if (cmp < 0)
{
node = node->left;
}
else
{
return node;
}
}
return NULL;
}
return NULL;
}
//下面开始做删除
void rbtree_delete_node(rbtree *t, rbtree_node *node)
{
if (t != NULL && node != NULL && t != t->nil)
{
//如果这个node左孩子与右孩子都有
//那么我们就要找一个能够替换它啥的结点
if (node->left != t->nil && node->right != t->nil)
{
//找后继进行替换
rbtree_node *successor = successors(node);
if (successor != t->nil)
{
//把键值给替换
node->key = successor->key;
node->value = successor->value;
//并且让node指向successor;
node = successor;
}
//如果有替换结点
//替换的是那一边
rbtree_node *replacement = (node->left != t->nil) ? node->left : node->right;
if (replacement == t->nil && node->parent == t->nil)
{
t->root = t->nil;
}
else if (replacement != t->nil)
{
//表明这个叶子结点有后继
//不管node->parent登不等于t->nil结点,
replacement->parent = node->parent;
if (node->parent == null)
{
t->root = replacement;
}
else if (node == node->parent->left)
{
node->parent->left = replacement;
}
else
{
node->parent->right = replacement;
}
//现在我们要把node游离出来
//目前断掉了node父亲指向node的指针
//开始剁掉node左右父的指向
node->left = node->right = node->parent = t->nil;
//其实按照c语言来说,需要把这个node指针给释放掉
free(node);
//如果删除的是黑色结点我们就必须进来调整
if (node->color == BLACK)
{
//调整
fix_after_delete(t, replacement);//注意要调整的是替换节点
}
}
else if (replacement == null)
{
//这里就是叶子结点
//叶子结点就先调整后删除
if (node->color == BLACK && node->parent != t->nil)
{
//不是根结点
//删除的是黑色结点,修正
fix_after_delete(t, replacement);
}
if (node->parent != NULL)
{
if (node == node->parent->left)
{
node->parent->left = NULL;
}
else
{
node->parent->right = NULL;
}
}
else
{
//这里是根结点
root = NULL;
}
//把当前叶子脱钩
node->parent = NULL;
free(node);
}
}
}
}
//下面我们要去修正一下这个方法
void fix_after_delete(rbtree *t, rbtree_node *node)
{
//删除整体来说,不需要递归
//一次删除一次结束
//我们先来考虑一下情况1 自己可以搞定的情况
//我说了删除都在叶子和叶子上面一层
//对于上面一层来说,上黑下红
//假如删除的是红色,就是叶子结点,考虑为不修正
//假如删除的是黑色,上面一层,会把红色拿来修正,怎么都不会进入下面的循环里面
while (node != t->root && node->color == BLACK)
{
//这里面就要考虑借的情况的
//全部删除的都是2结点,因为只有2结点才会出现删除不了的情况
//另外一点是结点可借的情况里面
//三结点与四结点共用一段颜色调整颜色调整的代码
//分析三结点可借的情况
//三结点中有一个小情况就是兄弟结点的孩子如果在左边就要转一下,把小的上去
//上面是右兄弟结点,如果是左兄弟,相反的转
//上面调整完之后,在转上去
//判定一下是左边的兄弟还是右边的兄弟
if (node == left_of(t, parent_of(t, node)))
{
//这里就是删除结点是左结点,删除是右结点的情况
//先来拿到兄弟结点
rbtree_node *rnode = right_of(t, parent_of(t, node));
//这里我们要来看一下是不是真正的兄弟结点
//如果兄弟为红色就表示不是真正的兄弟结点
if (rnode->color == RED)
{
//变为真正的兄弟结点
//就要想到把不是当前的兄弟给转上去
//当前的兄弟颜色是不对的
//父亲转下来,假兄弟转上去
//根据上黑下红的原则
//兄弟变黑,父亲变红
//这是右兄弟
//沿着父亲左旋
setColor(t, rnode, BLACK);
setColor(t, parent_of(t, node), RED);
rbtree_left_rotate(t, parent_of(t,node));
//更改为正确的兄弟
rnode = right_of(t, parent_of(t, node));//还是当前结点右边之是已经变了
}
//下面就要考虑情况二与情况三
//先来看兄弟没得借的情况,兄弟也是一个二结点
if (rnode->left == t->nil && rnode->right = t->nil)
{
//最后如果当前颜色是红色,不进来,跳出去变黑,平衡
setColor(t, rnode, RED);
//兄弟又自损一个维持平衡
x = parent_of(t, node);
}
else
{
//这里就是情况2,兄弟有得借的情况
//1.兄弟是三结点,或者四结点
//如果是三结点,考虑会不会有右左(右旋)的小情况
//要先旋转到右右上面
if (rnode->right == t->nil)
{
//兄弟结点与做孩子交换
//根绝上黑下红的原则
//兄弟变红,左孩子变黑
//以兄弟右旋上去
//改变真正的兄弟结点
setColor(t, left_of(t, rnode), BLACK);
setColor(t, rnode, RED);
rbtree_right_rotate(t, rnode);
rnode = right_of(t, parent_of(t, rnode));
}
//然后三四结点去借就开始真正的变色旋转
//兄弟要上去,变成父亲的颜色,这里考虑为红色
//父亲与兄弟右孩子搭下来,变黑,以父亲左旋结束
setColor(t, rnode, node->parent->color);
setColor(t, parent_of(t, node), BLACK);
setColor(t, right_of(t, rnode), BLACK);
rbtree_left_rotate(t, parent_of(t, node));
node = t->root;//结束
}
else
{
//这边就是左边是兄弟结点(我们的目的是找兄弟来借)
//这里就是删除结点是左结点,删除是右结点的情况
//先来拿到兄弟结点
rbtree_node *rnode = left_of(t, parent_of(t, node));
//这里我们要来看一下是不是真正的兄弟结点
//如果兄弟为红色就表示不是真正的兄弟结点
if (rnode->color == RED)
{
//变为真正的兄弟结点
//就要想到把不是当前的兄弟给转上去
//当前的兄弟颜色是不对的
//父亲转下来,假兄弟转上去
//根据上黑下红的原则
//兄弟变黑,父亲变红
//这是右兄弟
//沿着父亲左旋
setColor(t, rnode, BLACK);
setColor(t, parent_of(t, node), RED);
rbtree_right_rotate(t, parent_of(t,node));
//更改为正确的兄弟
rnode = left_of(t, parent_of(t, node));//还是当前结点右边之是已经变了
}
//下面就要考虑情况二与情况三
//先来看兄弟没得借的情况,兄弟也是一个二结点
if (rnode->right == t->nil && rnode->left = t->nil)
{
//最后如果当前颜色是红色,不进来,跳出去变黑,平衡
setColor(t, rnode, RED);
//兄弟又自损一个维持平衡
x = parent_of(t, node);
}
else
{
//这里就是情况2,兄弟有得借的情况
//1.兄弟是三结点,或者四结点
//如果是三结点,考虑会不会有右左(右旋)的小情况
//要先旋转到右右上面
if (rnode->left == t->nil)
{
//兄弟结点与做孩子交换
//根绝上黑下红的原则
//兄弟变红,左孩子变黑
//以兄弟右旋上去
//改变真正的兄弟结点
setColor(t, right_of(t, rnode), BLACK);
setColor(t, rnode, RED);
rbtree_left_rotate(t, rnode);
rnode = left_of(t, parent_of(t, rnode));
}
//然后三四结点去借就开始真正的变色旋转
//兄弟要上去,变成父亲的颜色,这里考虑为红色
//父亲与兄弟右孩子搭下来,变黑,以父亲左旋结束
setColor(t, rnode, node->parent->color);
setColor(t, parent_of(t, node), BLACK);
setColor(t, left_of(t, rnode), BLACK);
rbtree_right_rotate(t, parent_of(t, node));
node = root;//结束
}
}
}
}
//跳出循环,当前结点变黑
setColor(t, node, BLACK);
}