前提;关注+私信‘资料’MF分享相关资料。内容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,ffmpeg流媒体,CDN,P2P,K8S,Docker,Golang,TCP/IP,协程,嵌入式,ARM,DPDK等等。。。

红黑树是一个平衡的二叉树,但不是一个完美的平衡二叉树。虽然我们希望一个所有查找都能在~lgN次比较内结束,但是这样在动态插入中保持树的完美平衡代价太高,所以,我们稍微放松逛一下限制,希望找到一个能在对数时间内完成查找的数据结构。这个时候,红黑树站了出来。

阅读以下需要了解普通二叉树的插入以及删除操作。

红黑树是在普通二叉树上,对每个节点添加一个颜色属性形成的,同时整个红黑二叉树需要同时满足一下五条性质

红黑树需要满足的五条性质:

1.查找-在高度不在宽度

对于查找而言,如果一棵二叉树的高度是N,那么最多可以在N步内完成查找,这个不用解释,解释这个有点喧宾夺主了。这就是说,树的高度要尽可能矮。考虑到查找的平均情况,叶子节点到根节点的距离不能差别太大。 2.二叉树的不平衡根源

一棵树在查找看来变得不平衡是因为子树的高度相差很大。

二叉树为什么会这么容易变得不平衡,很简单,因为它只有二叉,左右均有50%的概率,那么插入N个节点全部都是左节点或者右节点的概率就是50%的N次方,如果是8叉树,那么这个概率就是12.5%的N次方,哪个概率大,自己算。

3.多叉树-宽度换高度

在第1节以及第2节,我们已经知道,树的宽度越大,高度越小,这样查询起来越快,Cisco路由器里不是有256叉乃至1024叉树吗?但是这样真的很好吗?对于稀疏节点,这样会严重消耗内存。
如果我们考虑CPU的MMU系统,就会知道,二级页表和三级页表的区别就在于对付稀疏地址空间的效果不同。

4.权衡-2,3树

我们发现,道生一,一生二,二叉树是一个完美的开始,但是我们发现它特别容易倾斜,倾斜的时候别触摸。我们也不能一下子就上256叉树,即使那样在海量节点情况下也抗不住,因此这种盲目宽度换高度的方案没有可扩展性。我们需要找出一种动态的机制,让一棵树动态调整保持平衡。
为了更加容易找出这个机制,让它更加容易现形,暂时不断增加树的宽度,如果增加到3叉树还找不到方案,就增加到4叉树...我们说的N叉树并不是说一个节点一定有N个子节点,而是说它最多有N个子节点。
迄今为止,以前都是我自己形而上的观点,几年前我的想法就到此为止,原因在于那段时间特别郁闷,就想找出些技术上的形而上思想,可是突然自己变好了,就没有继续下去。幸运的是,我现在发现确实有这么一个方案,而红黑树就是从3叉树回退过去的。
让我高兴的是,我的思路并没有跑偏。

5.2-3树的平衡变换

如果是二叉树,那么你插入一个节点,你只有最多1次机会保持子树的高度不变,如果是一个三叉树,那么就有2次机会。现在开始,我们为二叉树添了一叉,变成了三叉树。

这个可能有点理解困难,可以看图:

这个图片就是一个红黑树,NIL节点是个空节点,并且是黑色的。

性质四:每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);
就是连续的两个节点不能是连续的红色,连续的两个节点的意思就是父节点与子节点不能是连续的红色。

**性质五:从任一节点到其没个叶节点的所有路径都包含相同数目的黑色节点;**

还是看图:

从根节点到每一个NIL节点的路径中,都包含了相同数量的黑色节点。

这五条性质约束了红黑树,可以通过数学证明来证明,满足这五条性质的二叉树可以将查找删除维持在对数时间内。
当我们进行插入或者删除操作时所作的一切操作都是为了调整树使之符合这五条性质。
下面我们先介绍两个基本操作,旋转。
旋转的目的是将节点多的一支出让节点给另一个节点少的一支,旋转操作在插入和删除操作中经常会用到,所以要熟记。

下面是左旋和右旋:

左旋:

右旋:

下面讲讲插入

我们先明确一下各节点的叫法

因为要满足红黑树的这五条性质,如果我们插入的是黑色节点,那就违反了性质五,需要进行大规模调整,如果我们插入的是红色节点,那就只有在要插入节点的父节点也是红色的时候违反性质四或者是当插入的节点是根节点时,违反性质二,所以,我们把要插入的节点的颜色变成红色。

插入的新叶子节点的父节点是一个二叉节点

这种情况最简单,二叉节点变三叉节点即可,如下图所示:

)

.插入的新叶子节点的父节点是一个三叉节点这种情况比较复杂。树总是要长高的,保持平衡的方式就是同时长高,而这是不可能的,插入一个节点只能让该节点所在的子树长高。然而,如果能将这个信息上升到根部,在根部长高,就实现了“同时长高”!

还是循着上面的那个思路,我们继续增加树叉的数量,我们把它增加到4!新节点的插入如下图所示:

很遗憾,没有完成任务,但是最终我们提出了两个问题,只要解决了这两个问题,所有问题就解决了。
解决这两个问题,无疑都要牵扯到节点P的父节点以及再往上的节点,有两种可能:

可能性1:P的父节点PP是一个二叉节点

这个太爽,我们直接把P以及它的子树全部提到PP节点即可,类似B插入的情景,如下图所示

演进到红黑树

很显然,通过上面的描述,我们似乎找到了一个使树保持平衡的方案,而且是相当完美的平衡!核心就是宽度和高度之间的博弈。我们总是可以用一个宽度抵消一层高度,整个过程就是一次或者多次的一加一减,最终的结果还是0!

然而,这也不再是二叉树了,有的节点变成了三叉,并且保存了两个值,该两个值将区间分割成了三部分,是为三叉!因此在使用上就不如二叉树方便,比较操作复杂化了。事实上,将三叉节点处理成二叉节点,这棵树就成了红黑树!怎么处理呢?很简单!如下图所示:

红黑树的应用

它的统计性能要好于平衡二叉树(AVL树),因此,红黑树在很多地方都有应用。比如

①JDK1.8中的TreeMap 或
②C++的STL中的set和map函数或
③Linux内核中的rbtree.h和rbtree.c
都有红黑树的应用,这些集合均提供了很好的性能。
什么时候用AVL树?什么时候用红黑树?
首先AVL树和红黑树都是基于BST树(二叉搜索树),对于只读操作均和BST树相同,但BST树连续插入顺序递增或递减结点时会导致BST树退化成链表,性能大幅度降低。BST顺序插入如图:

总结;

总结;关注+私信‘资料’MF分享相关资料。内容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,ffmpeg流媒体,CDN,P2P,K8S,Docker,Golang,TCP/IP,协程,嵌入式,ARM,DPDK等等。。。