• 红黑树——原理


    1.性质

      红黑树是一种二叉查找树,但是每个节点增加一个表示结点颜色(红或黑)的字段,并且满足一下条件:

    • 每个节点或是红的,或是黑的
    • 根节点是黑的
    • 每个叶结点(NIL)是黑的
    • 如果一个节点是红的,则它的两个儿子都是黑的
    • 对每个节点,从该结点到其子孙节点的所有路径上包含相同数目的黑节点

      为方便处理边界条件,我们采用一个哨兵来表示NIL,如果某节点没有一个子结点或父结点,则该结点的相应指针指向NIL。

      一棵有n个内结点(不包括NIL)的红黑树高度之多为2lg(n+1)。

    2.旋转

      在红黑树上执行插入或删除操作时,结果都可能违反红黑树的性质,我们需要改变树的指针结构来保持红黑树的性质。旋转是修正过程中会用到的操作,分为左旋和右旋,这里只介绍左旋操作,右旋刚好与左旋操作相反。下面是旋转的示意图

      

    LEFT-ROTATE(T, x)
        y ← right[x]
        right[x] ← left[x]
    
        if left[y] != nil[T]
            p[left[y]] ← x
    
        p[y] ← p[x]
    
        if p[x] = nil[T]
            root[T] ← y
        else if x = left[p[x]]
            left[p[x]] ← y
        else right[p[x]] ← y
        
        left[y] ← x
        p[x] ← y

    3.插入

      插入操作与普通二叉树的插入操作接近,将新插入节点设置为红色(尽可能少违反二叉树性质),并在最后增加RB-INSERT-FIXUP(T, z)调节二叉树使其满足红黑树的性质。

    RB-INSERT
        y ← nil[T]
        x ← root[T]
        while x != nil[T]
            do y ← x
            if key[z] < key[x]
                x ← left[x]
            else x ← right[x]
        p[z] ← y
        if y = nil[T]
            root[T] = z
        else if key[z] < key[y]
            left[y] ← z
        else
            right[y] ← z
        left[z] ← nil[T]
        right[z] ← nil[T]
        color[z] ← RED
        RB-INSERT-FIXUP(T, z)

      RB-INSERT-FIXUP对插入结果进行修饰,可能会存在以下3中违反红黑树性质的情况(示例图假设“当前节点”为x,且父节点是祖父节点的左孩子,如果是右孩子,则将下面左右调转即可):

      case 1:父亲是红色的,叔叔也是红色的

        处理方法
        1.将“父节点”设为黑色
        2.将“叔叔节点”设为黑色
        3.将“祖父节点”设为红色
        4.将“祖父节点”设为当前节点

          

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

        处理方法:
        1.将“父节点”作为“当前节点”

        2.以“当前节点”为支点进行左旋

      

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

        处理方法:
        1.将“父节点”设为黑色。
        2.将“祖父节点”设为红色。
        3.以“祖父节点”为支点进行右旋。

      

      以下是RB-INSERT-FIXUP的过程:

    RB-INSERT-FIXUP(T, z)
      while color[p[z]] = RED            // 若“当前节点(z)的父节点是红色”,则进行以下处理。
        do 
          if p[z] = left[p[p[z]]]        // 若“z的父节点”是“z的祖父节点的左孩子”,则进行以下处理。
            y ← right[p[p[z]]]        // 将y设置为“z的叔叔节点(z的祖父节点的右孩子)”
            if color[y] = RED              // Case 1条件:叔叔是红色
              color[p[z]] ← BLACK         ▹ Case 1   //  (01) 将“父节点”设为黑色。
                  color[y] ← BLACK            ▹ Case 1   //  (02) 将“叔叔节点”设为黑色。
                  color[p[p[z]]] ← RED        ▹ Case 1   //  (03) 将“祖父节点”设为“红色”。
                  z ← p[p[z]]                 ▹ Case 1   //  (04) 将“祖父节点”设为“当前节点”(红色节点)
            else if z = right[p[z]]        // Case 2条件:叔叔是黑色,且当前节点是右孩子
                  z ← p[z]                    ▹ Case 2   //  (01) 将“父节点”作为“新的当前节点”。
                  LEFT-ROTATE(T, z)           ▹ Case 2   //  (02) 以“新的当前节点”为支点进行左旋。
            else                  // Case 3条件:叔叔是黑色,且当前节点是左孩子。
                  color[p[z]] ← BLACK         ▹ Case 3   //(01) 将“父节点”设为“黑色”。
              color[p[p[z]]] ← RED        ▹ Case 3   //  (02) 将“祖父节点”设为“红色”。
              RIGHT-ROTATE(T, p[p[z]])    ▹ Case 3   //  (03) 以“祖父节点”为支点进行右旋。
          
    // 若“z的父节点”是“z的祖父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。       else (same as then clause with "right" and "left" exchanged)   color[root[T]] ← BLACK

    4.删除

      与插入相似,删除操作也是基本与二叉查找树的删除操作相同,只是在最后要对树做RB-DELETE-FIXUP修复,使得树依然能满足红黑树的性质。

      第一步:将红黑树当作二叉查找树,将节点删除,分三种情况
      1.被删除节点没有儿子,直接将该节点删除
      2.被删除节点只有一个儿子,直接删除该节点,并用该节点的唯一子节点顶替它的位置
      3.被删除节点有两个儿子,先找出它的后继节点;然后把“它的后继节点的内容”与“该节点的内容”交换;之后,删除“它的后继节点”。

      第二步:对树做RB-DELETE-FIXUP操作,使得树满足红黑树的性质。RB-DELETE-FIXUP操作与RB-INSERT-FIXUP操作类似,不再做解释,以下是删除的过程:

    RB-DELETE(T, z)
      if left[z] = nil[T] or right[z] = nil[T]         
        y ← z                 // 若“z的左孩子” 或 “z的右孩子”为空,则将“z”赋值给 “y”;
      else y ← TREE-SUCCESSOR(z)      // 否则,将“z的后继节点”赋值给 “y”。
      if left[y] ≠ nil[T]
           x ← left[y]             // 若“y的左孩子” 不为空,则将“y的左孩子” 赋值给 “x”;
      else 
        x ← right[y]             // 否则,“y的右孩子” 赋值给 “x”。   p[x] ← p[y] // 将“y的父节点” 设置为 “x的父节点”   if p[y] = nil[T]   root[T] ← x // 情况1:若“y的父节点” 为空,则设置“x” 为 “根节点”。   else if y = left[p[y]]     left[p[y]] ← x // 情况2:若“y是它父节点的左孩子”,则设置“x” 为 “y的父节点的左孩子”   else right[p[y]] ← x // 情况3:若“y是它父节点的右孩子”,则设置“x” 为 “y的父节点的右孩子”   if y ≠ z   key[z] ← key[y] // 若“y的值” 赋值给 “z”。注意:这里只拷贝z的值给y,而没有拷贝z的颜色     copy y's satellite data into z   if color[y] = BLACK     then RB-DELETE-FIXUP(T, x) // 若“y为黑节点”,则调用   return y
    RB-DELETE-FIXUP(T, x)
      while x ≠ root[T] and color[x] = BLACK  
        do 
          if x = left[p[x]]      
            w ← right[p[x]]      // 若 “x”是“它父节点的左孩子”,则设置 “w”为“x的叔叔”(即x为它父节点的右孩子) 
          if color[w] = RED           // Case 1: x是“黑+黑”节点,x的兄弟节点是红色。(此时x的父节点和x的兄弟节点的子节点都是黑节点)。
            color[w] ← BLACK         ▹  Case 1   //   (01) 将x的兄弟节点设为“黑色”。
            color[p[x]] ← RED        ▹  Case 1   //   (02) 将x的父节点设为“红色”。
            LEFT-ROTATE(T, p[x])     ▹  Case 1   //   (03) 对x的父节点进行左旋。
            w ← right[p[x]]          ▹  Case 1   //   (04) 左旋后,重新设置x的兄弟节点。
          if color[left[w]] = BLACK and color[right[w]] = BLACK       
                           // Case 2: x是“黑+黑”节点,x的兄弟节点是黑色,x的兄弟节点的两个孩子都是黑色。         then color[w] ← RED ▹ Case 2 // (01) 将x的兄弟节点设为“红色”。         x ← p[x] ▹ Case 2 // (02) 设置“x的父节点”为“新的x节点”。       else if color[right[w]] = BLACK
                               // Case 3: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的左孩子是红色,右孩子是黑色的。         then color[left[w]] ← BLACK ▹ Case 3 // (01) 将x兄弟节点的左孩子设为“黑色”。         color[w] ← RED ▹ Case 3 // (02) 将x兄弟节点设为“红色”。         RIGHT-ROTATE(T, w) ▹ Case 3 // (03) 对x的兄弟节点进行右旋。         w ← right[p[x]] ▹ Case 3 // (04) 右旋后,重新设置x的兄弟节点。       else                // Case 4: x是“黑+黑”节点,x的兄弟节点是黑色;x的兄弟节点的右孩子是红色的。         color[w] ← color[p[x]] ▹ Case 4 // (01) 将x父节点颜色 赋值给 x的兄弟节点。         color[p[x]] ← BLACK ▹ Case 4 // (02) 将x父节点设为“黑色”。         color[right[w]] ← BLACK ▹ Case 4 // (03) 将x兄弟节点的右子节设为“黑色”。         LEFT-ROTATE(T, p[x]) ▹ Case 4 // (04) 对x的父节点进行左旋。         x ← root[T] ▹ Case 4 // (05) 设置“x”为“根节点”。     // 若 “x”是“它父节点的右孩子”,将上面的操作中“right”和“left”交换位置,然后依次执行。     else (same as then clause with "right" and "left" exchanged)     color[x] ← BLACK
  • 相关阅读:
    复习事件委托
    模式学习⑧--观察者模式
    模式学习⑦ --中介者模式
    模式学习(六)--- 策略模式
    模式学习(五)--装饰者模式
    模式学习(四)-迭代器
    模式学习(三)- 工厂模式
    模式学习(二)
    linux rpm包解压
    linux patch中的p0和p1的区别
  • 原文地址:https://www.cnblogs.com/yitong0768/p/4561825.html
Copyright © 2020-2023  润新知