目录
一、为什么要有红黑树
我们都知道搜索二叉树,但是最后我们发现搜索二叉树有缺陷,会退化成链表,之后我们又引入了AVL树。AVL树是一种高度平衡的二叉搜索树,能够满足增删查都是O(logN)的时间复杂度。但是由于AVL树是高度平衡的二叉搜索树,维持一棵AVL树的代价很高,而红黑树是一种弱平衡的二叉搜索树,其性能也不算查,所以在综合考虑下。许多情况都用红黑树。(如STL的map)。
二、红黑树的性质
红黑树具有以下性质:
- 定义1:每个节点都有颜色,要么是黑色,要么是红色。
- 定义2:根节点是黑色。
- 定义3:每个叶子节点(NULL)是黑色。
- 定义4:红色结点的子结点一定都是黑色。
- 定义5:任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
如下图就是一棵红黑树。
根据红黑树以上五条定义,我们可以推导出以下几点性质和可能情景:
- 性质1:父子节点不可能同时是红节点,即红节点不连续。否则违反定义4
- 性质2:如果某节点的一个子节点是黑色,那么该节点必然存在另一个子节点。否则违反定义5
- 情景1:一个结点的两个子节点出现一个是红色,一个是黑色的情况是可能出现的
- 情景2:父子节点同时是黑色的情况是可能出现的
三、平衡操作
在进行红黑树的插入、删除时候,很可能会破坏红黑树的性质。因此,我们需要一些平衡操作来维护红黑树性质不被破坏。主要有三种操作:变色、左旋、右旋。
我们先讲清楚这些平衡操作的执行过程,在下面的第四部分红黑树的操作中再加以说明平衡操作的使用情景。
1.变色
顾名思义,就是改变结点的颜色。红色变为黑色,黑色变为红色。
2.左旋
对x节点进行左旋,意味着"将x变成一个左节点"。
可以简单的记为:右儿子变老子,左孙子变右儿子。
动图如下。
3.右旋
对Y进行右旋,意味着"将Y变成一个右节点"。
可以简单的记为:左儿子变老子,右孙子变左儿子。
动图如下。
四、红黑树的操作
1.查找
因为红黑树是一种特殊的二叉搜索树,所以查找可以直接使用二叉搜索树的方法:当前节点与带查找值比较,小于左孩子往左,大于右孩子往右,等于当前节点就是找到,否则不存在。
2.插入
可以简单的记为:根节点必黑,新增是红色,只能黑连黑,不能红连红; 爸叔通红就变色,爸红叔黑就旋转,哪边黑往哪边转。
3.删除
五、红黑树的复杂度及应用
红黑树的增删查改的复杂度为O(lgN)。
红黑树的定义4和5中这样说道:如果一个节点是红色的,则它的子节点必须是黑色的;任意一结点到每个叶子结点的路径都包含数量相同的黑结点。
由此可以推出一个结论:一棵含有n个节点的红黑树的高度至多为2log(n+1).
这里不做证明,但是可以根据下图感性认识下。
最短路径就是全黑节点,最长路径就是一个红节点一个黑节点,最后黑色节点相同时,最长路径刚好是最短路径的两倍。
红黑树的应用
- Linux内核进程调度由红黑树管理进程控制块。
- Epoll用红黑树管理事件块。
- nginx服务器用红黑树管理定时器。
- C++ STL中的map和set的底层实现为红黑树。
- Java中的TreeMap和TreeSet由红黑树实现。
- Java8开始,HashMap中,当一个桶的链表长度超过8,则会改用红黑树。
六、扩展:红黑树与2-3-4树
红黑树中区分红色黑色节点究竟有什么意义呢?为什么要这样设置,以及旋转变色规则是怎么来的?这样什么就可以防止退化?
原来红黑树是概念模型2-3-4树的一种实现,由于直接进行不同节点间的转化会造成较大的开销,所以选择以二叉树为基础,在二叉树的属性中加入一个颜色属性来表示2-3-4树中不同的节点。
2-3-4树中的2节点对应着红黑树中的黑色节点,而2-3-4树中的非2节点是以红节点+黑节点的方式存在,红节点的意义是与黑色父节点结合,表达着2-3-4树中的3,4节点。
推荐看这篇:硬核图解面试最怕的红黑树【建议反复摩擦】