前言
对二叉平衡树不熟悉的读者,建议先阅读《20分钟搞定平衡二叉树(AVL树)【超详细】》。本文红黑树的内容将结合平衡二叉树知识点进行阐述!
1. 红黑树的介绍
红黑树,一种二叉查找树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。
通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出2倍,因而是接近平衡的。
红黑树,作为一棵二叉查找树,满足二叉查找树的一般性质:
(1)若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)任意节点的左、右子树也分别为二叉查找树。
(4)没有键值相等的节点。
因为一棵由n个结点随机构造的二叉查找树的高度为lgn,所以顺理成章,二叉查找树的一般操作的执行时间为O(logn)。但二叉查找树若退化成了一棵具有n个结点的线性链后,则这些操作最坏情况运行时间为O(n)。
红黑树虽然本质上是一棵二叉查找树,但它在二叉查找树的基础上增加了着色和相关的性质使得红黑树相对平衡,从而保证了红黑树的查找、插入、删除的时间复杂度最坏为O(logn)。
但它是如何保证一棵N个结点的红黑树的高度始终保持在logN的呢?这就引出了红黑树的5个性质:
- 每个结点要么是红的要么是黑的。
- 根结点是黑的。
- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
- 如果一个结点是红的,那么它的两个儿子都是黑的。 通俗来将,即两个红色结点不可以有父子关系上的连线。
- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
(注:叶子结点即为树尾端的NIL指针或NULL结点,此叶结点非子结点,它不包含数据而只充当树在此结束的指示)
2. 红黑树自平衡策略
(1)变色:红黑树的节点由红变黑或由黑变红
(2)左旋:以 某个节点作为支点(旋转支点),其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
(3)右旋:以 某个节点作为支点(旋转支点),其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
插入最多2次旋转达到平衡、删除最多3次旋转达到平衡;所以红黑树最多三次旋转达到平衡
左旋图示:
结合着动图再来理解下左旋的原理:
左旋:以 某个节点作为支点(旋转支点),其右子节点变为旋转节点的父节点,右子节点的左子节点变为旋转节点的右子节点,左子节点保持不变。
右旋图示:
结合着动图再来理解下右旋的原理:
右旋:以 某个节点作为支点(旋转支点),其左子节点变为旋转节点的父节点,左子节点的右子节点变为旋转节点的左子节点,右子节点保持不变。
3. 红黑树查找和插入
红黑树查找:
红黑树的查找遵循二叉搜索树查找的原则,从上到下,从左到右比较后找到目标结点的位置,并读取其值。比较简单,此处不赘述!
红黑树插入:
插入操作包括两部分工作:
(1)查找插入的位置
(2)插入后自平衡
注意:插入节点,必须为红色,理由很简单,红色在父节点(如果存在)为黑色节点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。
但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,必须做自平衡。
在开始每个情景的讲解前,我们还是先来约定下:
4. 红黑树插入节点情景分析 [超重要]
情景1:红黑树为空树
最简单的一种情景,直接把插入结点作为根结点就行。
注意:根据红黑树性质2:根节点是黑色。还需要把插入结点设为黑色。
情景2:插入结点的Key已存在
处理:更新当前节点的值,为插入节点的值
情景3:插入结点的父结点为黑结点
由于插入的结点是红色的,当插入结点的黑色时,并不会影响红黑树的平衡,直接插入即可,无需做自平衡。
情景4:插入节点的父节点为红色
再次回想下红黑树的性质2:根结点是黑色。如果插入节点的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点。
这一点很关键,因为后续的旋转操作肯定需要祖父结点的参与。
插入情景4.1:叔叔结点存在并且为红结点
依据红黑树性质4可知,红色节点不能相连 ==> 祖父结点肯定为黑结点;
因为不可以同时存在两个相连的红结点。那么此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红!
处理:
- 将P和U节点改为黑色
- 将PP改为红色
- 将PP设置为当前节点,进行后续处理
可以看到,我们把PP结点设为红色了,如果PP的父结点是黑色,那么无需再
做任何处理;
但如果PP的父结点是红色,则违反红黑树性质了。所以需要将PP设置为当前节点,继续做插入操作自平衡处理,直到平衡为止。
插入情景4.2:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的左子结点
注意:单纯从插入前来看,叔叔节点非红即空(NIL节点),否则的话破坏了红黑树性质5,此路径会比其它路径多一个黑色节点。
插入情景4.2.1:新插入节点,为其父节点的左子节点(LL红色情况)
处理:
- 变颜色:将P设置为黑色,将PP设置为红色
- 对PP节点进行右旋
插入情景4.2.2:新插入节点,为其父节点的右子节点(LR红色情况)
处理:
- 对P进行左旋
- 将P设置为当前节点,得到LL红色情况
- 按照LL红色情况处理(1.变颜色 2.右旋PP)
插入情景4.3:叔叔结点不存在或为黑结点,并且插入结点的父亲结点是祖父结点的右子结点
该情景对应情景4.2,只是方向反转,直接看图。
插入情景4.3.1:新插入节点,为其父节点的右子节点(RR红色情况)
处理:
- 变颜色:将P设置为黑色,将PP设置为红色
- 对PP节点进行左旋
插入情景4.3.2:新插入节点,为其父节点的左子节点(RL红色情况)
处理:
- 对P进行右旋
- 将P设置为当前节点,得到RR红色情况
- 按照RR红色情况处理(1.变颜色 2.左旋PP)
5. 红黑树和平衡二叉树到底哪些不同?
平衡二叉树(AVL树)和红黑树都是一种自平衡的二叉树。AVL树是严格平衡的二叉树,要求每个节点的左右子树的高度差不超过1,而红黑树则要求宽松一些,不追求完全平衡,只要求任何一条路径的长度不超过其路径长度的2倍(黑高性质的限制),任何不平衡都会在三次旋转之内解决。
正是由于这个差别,AVL树的查找效率会更高,但平衡调整的成本也更大。需要频繁查找时用AVL树更合适,在需要频繁插入和删除数时,选用红黑树更适合!
结语: 红黑树的相关知识点就介绍到这里,确实比较难以理解,建议读者反复看几次,等时间空余笔者会出一期Java版手撕红黑树源码供大家交流!
参考资料:
小刘讲源码: https://www.bilibili.com/video/BV1UJ411J7CU?p=1
青岛大学王卓:https://www.bilibili.com/read/cv3285768
声明:
特别鸣谢B站博主-小刘讲源码,授权本文推送。本文未声明图片以及文案内容均来自于小刘老师。欢迎到B站小刘老师个人主页学习更多Hashmap、红黑树、CAS等源码!
链接:https://pan.baidu.com/s/10FbsqrgtewZBUls9SiAzzA
提取码:eued