数据结构红黑树

Posted goodrnne

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构红黑树相关的知识,希望对你有一定的参考价值。

一、红黑树的定义:


   (1)根节点是黑色的,

  (2)所有叶子节点上不存储数据,并且颜色都为黑色。

  (3)红色节点相邻的节点不能为红色。(红色节点邻居节点只能是黑色节点)

  (4)每一个节点,从该节点到达可达叶子节点的所有路径都包含了相同数量的黑色节点。

     图示例子

  技术图片

 

  红黑树的时间复杂度为O(log n)。由平衡二叉树我们可以知道其高度约为 log2n(再添加数据时通过不断的调整达到平衡)。而红黑树的高度为多少呢?

  这里有一个定理:一个含有n个节点的红黑树的高度最多为2log n。我们可以这样来理解: 对于上面的那个图示,如果我们去掉所有的红色节点,结果会变成下图。

技术图片

 

  这时由于有些节点没有父节点了,我们直接拿到他的祖父节点来充当其父节点,所以之前的二叉树就变成了四叉树。在前面红黑树的定义里有这么一条:从任意节点到可达的叶子节点的每个路径包含相同数目的黑色节点。我们从四叉树中取出某些节点,放到叶节点位置,四叉树就变成了完全二叉树。所以,仅包含黑色节点的四叉树的高度,比包含相同节点个数的完全二叉树的高度log2n还要小。而现在当我们再把红色节点加回去时,由于前面的定义红色点之间不能相邻,所以现在红黑树的高度会比log2n大,但是最大不会超过2log2n(因为之前全部黑色节点时,的高度不会超过log2n)。

  总结

  因此,由于红黑树的高度只比平衡树的高度大了一倍,但是在性能上,下降的并不是很多。在实际运用中,AVL 树是一种高度平衡的二叉树,查找的效率非常高,但是,AVL 树为了维持这种高度的平衡,就需要进行更多的操作来保持这种平衡。每次插入、删除都要做调整,就比较复杂、耗时。所以,对于有频繁的插入、删除操作的数据集合,使用 AVL 树的代价就有点高了。而红黑树只是做到了近似平衡,并不是严格的平衡,所以在维护平衡的成本上,要比 AVL 树要低。所以,红黑树的插入、删除、查找各种操作性能都比较稳定。对于工程应用来说,要面对各种异常情况,为了支撑这种工业级的应用,我们更倾向于这种性能稳定的平衡二叉查找树。

二、红黑树的操作


   在插入数据和删除数据时,都可能会破坏红黑树的定义。因此我们需要通过一定的方法进行调整来使得红黑树达到满足定义和平衡。

  在红黑树调整策略中有两个基本的操作就是左旋和右旋。

  左旋

技术图片

  左旋的处理规则(有助于理解旋转规则) 

 1 LEFT-ROTATE(T, x)  
 2  y ← right[x]            // 前提:这里假设x的右孩子为y。下面开始正式操作
 3  right[x] ← left[y]      // 将 “y的左孩子” 设为 “x的右孩子”,即 将β设为x的右孩子
 4  p[left[y]] ← x          // 将 “x” 设为 “y的左孩子的父亲”,即 将β的父亲设为x
 5  p[y] ← p[x]             // 将 “x的父亲” 设为 “y的父亲”
 6  if p[x] = nil[T]       
 7  then root[T] ← y                 // 情况1:如果 “x的父亲” 是空节点,则将y设为根节点
 8  else if x = left[p[x]]  
 9            then left[p[x]] ← y    // 情况2:如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
10            else right[p[x]] ← y   // 情况3:(x是它父节点的右孩子) 将y设为“x的父节点的右孩子”
11  left[y] ← x             // 将 “x” 设为 “y的左孩子”
12  p[x] ← y                // 将 “x的父节点” 设为 “y”

  右旋

技术图片

 旋的处理规则:

 1 RIGHT-ROTATE(T, y)  
 2  x ← left[y]             // 前提:这里假设y的左孩子为x。下面开始正式操作
 3  left[y] ← right[x]      // 将 “x的右孩子” 设为 “y的左孩子”,即 将β设为y的左孩子
 4  p[right[x]] ← y         // 将 “y” 设为 “x的右孩子的父亲”,即 将β的父亲设为y
 5  p[x] ← p[y]             // 将 “y的父亲” 设为 “x的父亲”
 6  if p[y] = nil[T]       
 7  then root[T] ← x                 // 情况1:如果 “y的父亲” 是空节点,则将x设为根节点
 8  else if y = right[p[y]]  
 9            then right[p[y]] ← x   // 情况2:如果 y是它父节点的右孩子,则将x设为“y的父节点的左孩子”
10            else left[p[y]] ← x    // 情况3:(y是它父节点的左孩子) 将x设为“y的父节点的左孩子”
11  right[x] ← y            // 将 “y” 设为 “x的右孩子”
12  p[y] ← x                // 将 “y的父节点” 设为 “x”

  插入操作

  红黑树规定插入的节点必须是红色,并且插入的节点都是放在叶子节点上。因此,对于插入操作有两种特殊的情况比较好处理:

  (1) 插入节点的父节点是黑色的,这种情况满足红黑树的定义,因此不需要做什么操作。

  (2)如果插入的节点是根节点,我们只需要将其变成黑色的就行了。

  其他的情况他都会破坏红黑树的定义,因此需要借助左右旋或改变颜色来进行调整。

技术图片

  case 1:当前节点的父节点是红色,且当前节点的祖父节点的另一个子节点(叔叔节点)也是红色。

技术图片

  case 2:当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的右孩子。

技术图片

  case 3:当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

技术图片

 删除操作


 待续

以上是关于数据结构红黑树的主要内容,如果未能解决你的问题,请参考以下文章

红黑树探索笔记

码图并茂红黑树

数据结构-红黑树

数据结构——红黑树

红黑树探索

C高级 框架开发中红黑树结构