如果我们给二叉树加一个额外的条件,就可以得到一种被称作二叉搜索树(Binary Search Tree)的特殊二叉树
二叉搜索树要求:
虽然二叉搜索树在最优情况下(完全二叉树)平均查找比较次数为log2(n)(二分查找)
但是在极端情况下二叉树退化为线性链表查找时间复杂度就退化为O(log2n),这一切都是因为不够平衡导致的,所以需要平衡二叉查找树
2-3-4树即4阶B树,是一种完美平衡的树形结构,保证其查找的效率为O(log2n)级别
根据4阶B树的定义,可以知晓每个树结点最少有一个关键字,最多有三个关键字,所以可以把结点根据关键字个数分为:
2-3-4树虽然从理论上来说能解决二叉查找树的缺点,但是实现非常麻烦,要同时维护三种数据结构 2-结点 3-结点 4-结点。在三种数据结构之间转换复制信息不仅仅代码复杂而且需要额外的时间和空间开销,最后的结果也没理论那么好
虽然2-3-4树实现比较复杂,但我们仍然想通过二叉树结构描述2-3-4树,所以提出了红黑树这种数据结构,红黑树的背后逻辑就是2-3-4树的逻辑,是2-3-4树的二叉表示
那怎么用二叉树表示2-3-4树呢?
2结点用二叉树结构表示当然没问题,3结点、4结点怎么办?
多个关键字在二叉树中只能形成多个结点,因为二叉树中一个结点最多有两个孩子,加起来刚好三个结点,所以我们可以用红边(红链接)来描述3结点与4结点,即红边连接的结点中的关键字,在2-3-4树中是一个结点内的,这样就解决了34结点的表示问题
但是实际怎么存储呢?我们将红边指向的结点作为红结点,否则为黑结点,存在结点属性中
好了既然有了上面的结点对应关系我们来看一个实际等价转换例子
经这样二叉表示的2-3-4树性质很明显,简单推敲一下就是下方红黑树的五大性质
红黑树是每个结点都带有颜色属性的二叉查找树,颜色或红色或黑色,在二叉查找树强制一般要求以外,增加了如下的额外要求:
性质1:每个结点或者是黑色,或者是红色
性质2:根结点是黑色
性质3:所有叶子结点(NIL)是黑色
性质4:红色结点子结点一定是黑色,不可以同时存在两个红色结点相连
红黑树是AVL树吗?
红黑树并不是一颗平衡二叉查找树,从图上可以看到,根结点的左子树显然比右子树高
但左右子树黑结点的层数是相等的,即任意一个结点到到其每个叶子结点的路径都包含数量相同的黑结点(性质5)
所以我们叫红黑树这种平衡为黑色完美平衡
只要一棵二叉查找树树满足红黑树的五条性质,这棵树就是趋近于平衡状态的
对红黑树是 “近似平衡” 的理解
平衡二叉查找树的初衷是为了解决二叉查找树因为动态更新导致的性能退化问题。所以"平衡"的意思可以等价为性能不退化,”近似平衡"就等价为性能不会退化的太严重
一棵极其平衡的二叉树(满二叉树或完全叉树)的高度大约是 log2n,所以如果要证明红黑树是近似平衡的,只需要分析,红黑树的高度是否比较稳定地趋近 log2n 就好了
红黑树能自平衡,它靠的是什么?
靠的是三种操作:左旋、右旋和变色
1.变色:结点的颜色由红变黑或由黑变红
2.左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变
3.右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变
树的查找都取决于其高度差,红黑树相比于平衡二叉树是不严格的,因为它不是严格控制左、右子树高度差小于等于1,但红黑树高度依然是平均log2n,且最坏情况高度不会超过2log2n,即当一棵子树全为黑结点而另一颗红黑交替时。所以其查找时间复杂度:
T ( n ) = O ( l o g 2 n ) T(n)=O(log_2n) T(n)=O(log2n)
插入操作包括两部分操作
注意:插入结点,必须为红色,理由很简单,红色在父结点(如果存在)为黑色结点时,红黑树的黑色平衡没被破坏,不需要做自平衡操作。但如果插入结点是黑色,那么插入位置所在的子树黑色结点总是多1,每次都必须做自平衡
插入的所有情况分析
所有只有在插入的结点的父结点为红结点的情况下才需要平衡调整
最简单的一种情景,直接把插入结点作为根结点就行
注意:根据红黑树性质2根结点是黑色。还需要把插入结点设为黑色
新结点的value值替换原结点的value值
直接插入,不会影响自平衡
再次回想下红黑树的性质2:根结点是黑色
如果插入结点的父结点为红结点,那么该父结点不可能为根结点,所以插入结点总是存在祖父结点且为黑色
这一点很关键,因为后续的旋转操作肯定需要祖父结点的参与
此时该插入子树的红黑层数的情况是:黑红红。显然最简单的处理方式是把其改为:红黑红
处理:
可以看到,我们把PP结点设为红色了,如果PP的父结点是黑色,那么无需再做任何处理;
但如果PP的父结点是红色,则违反红黑树性质了。所以需要将PP设置为当前结点,继续做插入自平衡处理,直到平衡为止
注:单纯从插入前来看,叔叔结点非红即空(NIL结点),否则的话破坏了红黑树性质5,此路径会比其它路径多一个黑色结点。
处理:
处理:
该情况是对应情景4.2的镜像情况
处理:
处理:
注:情景4.2与4.3的叔叔为黑结点的情况在第一次调整是不会出现的
的情况对应的是向2-3-4树中的4结点再次插入关键字导致的结点分裂的情况
按照4阶B树结点分裂情况来看,会从中间位置 ⌈ m / 2 ⌉ \lceil m/2 \rceil ⌈m/2⌉处将其关键字分为两部分,左部分包含的关键字放在原结点中,右部分包含关键字放在新结点中,中间关键字插入其父节点:即一个4结点会分裂为一个3结点和一个2结点
这四种插入后失衡情况其实都是一个原理,其对应的是2-3-4树中的3结点变为4结点,只不过位置可能插的不到位,没有插入到3结点空的那一颗子树上去,所以需要变色旋转内部调整平衡即可
插入操作包括两部分操作
查找操作的时间复杂度为O(log2n),而由于自平衡操作最坏情况下也只是需要两次旋转操作即时间复杂度为O(1),所以效率:
T ( n ) = O ( l o g 2 n ) T(n)=O(log_2n) T(n)=O(log2n)
我们先来简单回顾下二叉搜索树的删除操作。假如我们有一颗二叉搜索树如下:
二叉搜索树中删除结点有3种场景:
结论:所有删除最终都会退化为删除单个结点
红黑树删除的所有情况如下:
将key互换然后再删除红子结点即可
转换为删除单个结点
转换为删除带有一个子树的结点 => 转换为删除单个结点
注:以上为了好画图,转而删除的单个结点都是红结点
直接做掉就vans了,也并不会影响平衡
注:以下删除结点都是以被删除结点为左孩子的情况,被删除结点为右孩子的情况镜像处理即可
此时无论父亲是红色还是黑色都是一样的
解决:左旋父,爷染父色,父叔黑
此时无论父亲是红色还是黑色都是一样的
解决:右旋兄,交换兄弟与其右子颜色,变成情况5.1
此时,两个侄子结点一定不存在(Nil)(因为存在就属于前两种情况)
解决:兄弟红,向上找,遇根或红结点,染黑即解决
如果父结点是非根黑结点则递归处理父节点:
此时,父结点一定是黑结点
解决:左旋父,父祖换色,变成前三种情况
此时,以目标结点D为参照,根据黑兄弟BL以及侄子结点情况可能变成前三种情况
这其实是黑兄弟双黑侄的镜像情况,受的是3结点父亲的二叉表示不唯一的影响
至此,删除结点已分析完毕。待删除结点是父结点的右孩子的情况为以上的镜像情况
在解释删除失衡能解决的原理之前我们很有必要来复习一下B树的删除:
众所周知B树中删除就是找兄弟结点借关键字:
此情况的失衡处理过程相当于2-3-4中的删除2结点,当然存在红右侄说明兄弟节点肯定不是2结点(暗示关键字够够的能借)
此情况其实是和黑兄弟,红右侄是一样的,只不过我们的2结点兄弟结点的二叉表示可能不利于我们调整
此情况的失衡处理过程相当于2-3-4中的删除2结点,当然存在红右侄说明兄弟节点肯定不是2结点(暗示关键字够够的能借)
这种情况说明兄弟也是2结点不够借了,只能将关键字删除后与兄弟结点及双亲结点中的关键字进行合并
当向上找遇到根结点时将根节点与不够借的兄弟结点合并为一个3节点即可,当遇到红结点说明父节点非2结点(合并完之后父亲关键字 数量还够),则将父节点的红子接点与兄弟结点合并为一个2结点即可
当遇到非根黑结点说明父节点是一个2结点,父节点与兄弟结点合并为一个三结点之后关键字数量的话,则又要与其自己的兄弟节点进行调整或者合并操作,直至平衡为止。下图是一个例子:
这其实是黑兄弟双黑侄的镜像情况,受的是3结点父亲的二叉表示不唯一的影响
调整相当于在2-3-4中没变但是3结点父亲的二叉表示变了
注:在平衡性调整中首次就是红兄弟这种情况调整后只能转换到黑兄弟双黑侄,其它情况会在黑兄弟双黑侄视角上移中体现
删除操作包括两部分
查找操作的时间复杂度为O(log2n),且当失衡时RBTree最多只需3次旋转就可调整平衡,只需要O(1)的复杂度。所以效率:
T ( n ) = O ( l o g 2 n ) T(n)=O(log_2n) T(n)=O(log2n)
查找 | 插入 | 删除 | |
---|---|---|---|
AVL树 | O(log2n) | O(log2n) | O(log2n) |
红黑树 | O(log2n) | O(log2n) | O(log2n) |
AVL树的从查找时间复杂度虽然略优于红黑树,但是对于现在的计算机,cpu太快,可以忽略性能差异
红黑树整体统计性能略优于AVL树(红黑树旋转情况少于AVL树)
详细分析如下:
查找:红黑树属于不严格的平衡二叉树。因为它不是严格控制左、右子树高度或结点数之差小于等于1,但红黑树高度依然是平均log(n),且最坏情况高度不会超过2log2n,所以查找效率依然是log(n)级别
插入:插入node引起了树的失衡,AVL和RBTree都是最多只需要2次旋转操作,即两者都是O(1)
删除:在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root路径上所有node的平衡性,因此需要旋转的量级O(log2n),而RBTree最多只需3次旋转,只需要O(1)的复杂度
总结:AVL的结构相较RBTree来说更为平衡,在插入和删除node更容易引起Tree的不平衡,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RBTree在需要大量插入和删除node的场景下效率更高。自然,由于AVL高度平衡,因此AVL的搜索效率更高
若想了解红黑树时间复杂度证明可以看一下这篇文章:RBTree时间复杂度分析
PS:其实文章还少些东西没怎么写完,过两天修补下
传送门:Gayhub地址