• 【数据结构】红黑树


    一、红黑树的定义:


       (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:当前节点的父节点是红色,叔叔节点是黑色,且当前节点是其父节点的左孩子

     删除操作


  • 相关阅读:
    java 动态代理
    android中几个很有用的的api
    android 静态和动态设置 Receiver的 android:enabled值
    一个文件查看你选择 Run as Android applications 都干了啥
    ViewStub 的使用
    Linux 常用命令速查
    android自定义View&&简单布局&&回调方法
    西厢记 随笔
    git 命令使用速查手册( 个人版)
    Arraylist源码分析:
  • 原文地址:https://www.cnblogs.com/GoodRnne/p/10661401.html
Copyright © 2020-2023  润新知