![6d9dd219f86fde2953b3c76cc773949d.png](https://i-blog.csdnimg.cn/blog_migrate/4fc7236fc2e8f045bc23a41c3f045e7a.jpeg)
一、介绍
简介
红黑树是一颗十分平衡的二叉查找树。 为了防止出现二叉树最坏情况(瘸子二叉树)出现,优化链表的查找性能而发明的一种数据结构。红黑树的颜色是保证红黑树查找速度的一种方式,从任意的节点开始到叶节点的路径,黑节点的个数是相同的,这就能保证搜索路径的最大长度不超过搜索路径的最短长度的2倍。
二叉查找树
![e3676c1c61ea29ac70d2f9723b23315b.png](https://i-blog.csdnimg.cn/blog_migrate/ae1b91dae214f09cfc016f0f04588125.png)
应用场景
- 广泛用在C++的STL中。map和set都是用红黑树实现的。
- 著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块。
- epoll在内核中的实现,用红黑树管理事件块
- nginx中,用红黑树管理timer等
- Java的TreeMap,HashMap实现
特点
- 1.节点是红色或黑色。
- 2.根节点是黑色, 红链接均为左链接。
- 3.每个叶子节点都是黑色的空节点(NIL节点)。
- 4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
与AVL树的对比
![4264710f5e9f68be05bad0d14ad55c00.png](https://i-blog.csdnimg.cn/blog_migrate/8431d67bc0ff06c993a786f36d19b47a.jpeg)
- 1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
- 2、平衡二叉树追求绝对平衡,条件比较苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
二、原理
红黑树本质上就是二叉查找树的升级版,只要在插入和删除这种可能打破红黑树规则的方法上加上判断是否需要进行平衡化操作就可以了,其他都跟二叉查找树一样。
左旋
左旋:当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋。
![103a84e115696ff6ff61fdf6cd9221f9.png](https://i-blog.csdnimg.cn/blog_migrate/4c74cb0ddecd697b0f09c0cf6b8270b4.png)
/**
* 左旋转
* 当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋。
*
* @param h
* @return
*/
//当前结点为h,它的右子结点为x
private Node rotateLeft(Node h) {
Node x = h.right;
//让x的左子结点变为h的右子结点
h.right = x.left;
//h成为x的左子节点
x.left = h;
//h的颜色赋值给x
x.color = h.color;
//让h(左子节点)颜色变成red
h.color = RED;
return x;
}
右旋
右旋:当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋
![8f4451c6e5f633c8c8cd32b1374fe90b.png](https://i-blog.csdnimg.cn/blog_migrate/18d5ce9bf0bf27f443afc40fcd7df921.png)
/**
* 右旋
* 当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋
*
* @param h
* @return
*/
//当前结点为h,它的左子结点的左子结点为x
private Node rotateRight(Node h) {
Node x = h.right.right;
//让x的右子结点变为h的左子结点
h.left = x.right;
//让h变成x的右子结点
x.right = h;
//h的颜色赋值给x
x.color = h.color;
//h颜色变为红色
h.color = RED;
return x;
}
反转
颜色反转:当一个结点的左子结点和右子结点的color都为RED时,需要反转,把左子结点和右子结点的颜色变为BLACK,同时让当前结点的颜色变为RED即可。
![c0eb7605e3343226ea3da5e29e3f3e8b.png](https://i-blog.csdnimg.cn/blog_migrate/09fcac7ee1ed9cbd83a825b3543e2f43.png)
/**
* 颜色反转,相当于完成拆分4-节点
* 当一个结点的左子结点和右子结点的color都为RED时,需要反转
* 把左子结点和右子结点的颜色变为BLACK,同时让当前结点的颜色变为RED即可。
*
* @param h
*/
private void flipColors(Node h) {
//当前结点变为红色
h.color = RED;
//左子结点和右子结点变为黑色
h.left.color = BLACK;
h.right.color = BLACK;
}
三、实现
插入
下面追加的三个判断才是二叉查找树和红黑树的区别。
/**
* 在整个树上完成插入操作
*
* @param key
* @param val
*/
public void put(Key key, Value val) {
root = put(root, key, val);
//根结点不存在父节点,所以它的颜色总是黑色
root.color = BLACK;
}
/**
* 在指定树中,完成插入操作,并返回添加元素后新的树
* @param h
* @param key
* @param val
*/
private Node put(Node h, Key key, Value val) {
if (h == null) {
N++;
//每次插入的节点初始颜色都为红色
return new Node(key, val, null, null, RED);
}
//根据查找树规则添加元素
int cmp = key.compareTo(h.key);
if (cmp < 0) {
h.left = put(h.left, key, val);
} else if (cmp > 0) {
h.right = put(h.right, key, val);
} else {
//相等则做更新操作
h.value = val;
}
//进行左旋:当当前结点h的左子结点为黑色,右子结点为红色,需要左旋
if (isRed(h.right) && !isRed(h.left)) {
h = rotateLeft(h);
}
//进行右旋:当当前结点h的左子结点和左子结点的左子结点都为红色,需要右旋
if (isRed(h.left) && isRed(h.left.left)) {
rotateRight(h);
}
//颜色反转:当前结点的左子结点和右子结点都为红色时,需要颜色反转
if (isRed(h.left) && isRed(h.right)) {
flipColors(h);
}
return h;
}
测试结果
![50e853c9dc3f59f8eaaac0611a12df5a.png](https://i-blog.csdnimg.cn/blog_migrate/cce1023c66bb1109cca65a4e779d3d28.jpeg)
四、常见问题
红黑树时间复杂度
对于一棵普通的平衡二叉搜索树来说,它的搜索时间复杂度为O(logn),而作为红黑树,存在着最坏的情况,也就是查找的过程中,经过的节点全都是原来2-3树里的3-节点,导致路径延长两倍,时间复杂度为O(2logn),由于时间复杂度的计算可以忽略系数,因此红黑树的搜索时间复杂度依然是O(logn)。当然,由于这个系数的存在,在实际使用中,红黑树会比普通的平衡二叉树(AVL树)搜索效率要低一些。
在查询中,红黑树的效率比AVL树的效率低,但是由于两棵树的特性,在增删中,红黑树效率会更高。
思维导图
![c0193b1116690408d28ed9fc9b6d8c17.png](https://i-blog.csdnimg.cn/blog_migrate/9624c7d6099dd38f14fd245b4e41e57d.jpeg)