美文网首页
3-x 插播rb tree

3-x 插播rb tree

作者: db24cc | 来源:发表于2022-06-22 21:34 被阅读0次

    agenda

    1. 缘由

    2. rb tree properties

    3. rotation

    3. insertion

    4. deletion

    缘由

    之前看到stg-stl priority queue时觉得代码就是introduction to algorithms中大顶堆再工程化实现
    关系型容器的一个基础是rb tree, 有必要把它先弄清楚再看代码, 这个插播系列也是给自己后面review提供一个索引

    rb tree properties

    properties:

    A binary tree is a rb tree if it satisfies:

    1. every node is either red or black
    2. the root is black
    3. every leaf (NIL) is black
    4. if a node is red, then both it's children are black
    5. for each node, all paths from the node to descendant leaves contain the same number of black nodes.

    some symbol reference:

    • it ref as p#[1-5], leaf and parent are all NIL which is black
    • l(x) imply left child of x, r(x), p(x) in the same way
    • c(x) = RED imply mark node x as RED
    • NIL(T) refer sentinel of rb tree T, root(T) is the same way
    • Case#1 ref code handle situation
    • c#[1-15] ref code from line 1 to 15

    black height

    bh(x): black-height of the x is the number of black nodes on any path from a node x (but not including x) down to a leaf.
    from p#5 black height is well defined, since it shares all leaf of the same count.

    Lemma 13.1 a rb tree with n nodes has height at most 2lg(n+1)
    prove:
    first we use hypothesis show subtree rooted at any node x contains at least 2^{bh(x)} - 1 internal nodes.
    basis hypothesis: if h(x) = 0, then x is leaf(NIL), the subtree contains 2^{bh(x)} - 1 = 2^0 - 1 = 0 nodes, basis holds.
    inductive hypothesis: for node x, h(x) > 0 with 2 children.
    from p#4 --> bh(x) = \begin{cases}bh(x_{parent}), c(x_{parent}) == BLACK \\bh(x_{parent}) - 1, c(x_{parent}) == RED\end{cases} (since x_{parent} is RED x must be BLACK, excluding x make make bh(x) less 1 than bh(x_{parent}))
    by hypothesis we have number of internal nodes: counts(x) \ge 2^{bh(x)} - 1
    \therefore counts(x_{parent}) = counts(x_l) + count(x_r) + 1 \ge 2^{bh(x_{parent}) -1} - 1 +2^{bh(x_{parent} )- 1} - 1 + 1 = 2^{bh(x_{parent})} - 1 which prove claims
    \because according to p#4 a red node must least bring a black node on the way to the leaf
    \therefore at least half the nodes on any simple from the root to a leaf(not including the root) must be black. that is bh(x) \ge h(x)/2
    \therefore counts(x) \ge 2^{bh(x)} - 1 \ge 2^{h/2} - 1, h \le 2lg(counts(x) + 1), that is we get upper bound of height with relation of lg(n)

    rotations

    a rotation operation preserves the binary search tree property: the keys in a precede key[x], key[y], and keys in r, as show in graph

    void left_rotate(T, x){
      y = r(x); //buf y
      r(x) = l(y);//x's right -> β
      p(l(y)) = x; // β's parent -> x
      p(y) = p(x); // y's parent -> x's parent
      if (px(x) == NIL(T)): //x is root
        root(T) = y;
      else if (x == l(p(x))) // x is left child
        l(p(x)) = y;
      else
        r(p(x)) = y;
      l(y) = x;
      p(x) = y;
    }
    void right_rotate(T, y){
      x = l(y);// buf x
      l(y) = r(x);// y's left -> β
      p(r(x)) = y;// β's parent -> y
      p(x) = p(y);  x's parent -> y's parent
      if (p(y) == NIL(T)) // y is root
        root(T) = x;
      else if y == l(p(y)) // y is left child
        l(p(y)) = x;
      else
        r(p(y)) = x;
      r(x) = y;
      p(y) = x;
    }
    

    left_rotate <-> right_rotate are symmetric

    insertion

    insertion rb tree do as binary search tree then fix up fulfill rb tree properties.

    code snap

    void rb_insert(T, z){
      y = NIL(T);
      x = root(T);
      while (x != NIL(T)){ //find proper leaf position store in y
        y = x;
        if (k(z) < k(x))
          x = l(x);
        else
        x = r(x);
      }
      p(z) = y; // z's parent -> y
      if (y == NIL(T)) //not enter while loop, z is root
        root(T) = z;
      else if (k(z) < k(y))
        l(y) = z;
      else
        r(y) = z;
      }
      l(z) = r(z) = NIL(T);
      c(z) = RED;
      rb_insert_fixup(T, z);
    }
    

    the key point is rb_insert_fixup, we add new RED node at leaf position, codes as fellow:

    void rb_insert_fixup(T, z){
     1  while(c(p(z)) == RED) {
     2    if (p(z) == l(p(p(x)))) {
     3      y = r(p(p(z))); 
     4      if (c(y) == RED){ //Case#1
     5        c(p(z)) = BLACK;
     6        c(y) = BLACK;
     7        c(p(p(z))) = RED;
     8        z = p(p(z));
     9     } else if (z == r(p(z))){ //Case#2
    10      z = p(z);
    11      left_rotate(T, z);
    12    else { c(p(z)) = BLACK;//Case#3
    13       c(p(p(z))) = RED;
    14      right_rotate(T, p(p(z)));
    15  else ... exchange `left` <-> `right` do line c#2-14
    16  c(root(T)) = BLACK;  
    }
    

    correctness

    FIRST we see by call of rb_insert, it results violates p#2,4

    1. p#2 is break when z is root, c#16 fix it
    2. p#4 is break when z and p(z) are both RED

    for p#1,3,5 does not affect(since insert one is RED node)

    SECOND we check loop invariant before rb_insert_fixup and at each case of start of iteration, c#1-15 maintains the fellowing three part loop invariant are:
    a. z is RED
    b. if p(z) is the root, p(z) is BLACK (imply p(z) is RED, p(p(x)) exists)
    c. p#2,4 at most one violation.
    loop invariant is true prior to the first iteration of the loop. each loop maintains the loop, it make legal rb tree at loop termination.

    Initialization (prior call of rb_insert_fixup)
    a. newly add z is RED
    b. if p(z) is root, p(z) started out BLACK not change prior the call of rb_insert_fixup
    c. p#1,3,5 hold when rb_insert_fixup is called
     1. if p#2 is not hold, z must be root, it's children are the sentinel, which is BLACK, not a violation of p#4
     2. if p#4 is not hold, z & p(z) are RED, the tree had no other violations prior to z, hence not violation of p#2
    thus p#2 & p#4 at most one violation.

    Termination (loop finish)
    when loop terminates, p(z) is BLACK, no violation of p#4, p#2 is fixed by c#16, when rb_insert_fixup terminates all rb tree properties hold.

    Maintains (each case)
    there are 6 cases of cases, 3 of then are symmetric (depend on p(z) is left or right of p(p(z)))
    from c#2 check p(z) == RED because b of loop invariant, p(z) can not be root, that's p(p(z)) is exists, otherwise (p(z) == BLACK) loop terminates

    Case#1
    distinguish from Case2,3 by uncle's color
    In Case#1 p(z), y, z are RED, p(p(z)) is BLACK, making c(y) = c(p(z)) = BLACK, p(p(z)) = RED maintains p#5 then repeat z = p(p(z)) move up 2 levels.
    as shows:

    loop invariant
    a. after loop, new z = z' is p(p(z)) is RED
    b. if p(z') is root, and it not affected in this loop, remain BLACK at the next iteration
    c. Case#1 maintain p#5, not introduce p#1,3
      if z' is root, violation of p#2 only
      if z' is not root, z''s color is not related with p#2, we color p(z) and y to BLACK to fix z and p(z) both RED thereby maintain p#5
      if p(z') is BLACK rb tree properties hold, otherwise introduce violation of p#4 only and loop continue

    Case2,3
    In Case2,3, y is BLACK, then check z is left/right of p(z)'s child, if z == r(p(z)) calling left_rotate transform from Case#2 to Case#3, else enter directly into Case#3
    In Case2,3, because both z and p(z) are RED, the rotation affects neither the black height of nodes nor p#5 when y is BLACK
      in Case#2, c#10 move up one level and down one level in c#11 the identity of p(p(z)) remain unchanged.
      in Case#3, color change and right rotation preserve p#5, no longer have 2 RED in one row, p(z) is BLACK loop terminates.

    loop invariant
    a. Case#2 while loop finish z now points previous p(z)(in graph node A) which is RED, no further change z and it's color
    b. Case#3 make p(z) to BLACK, if p(z) is root, at next start of iteration, it's BLACK
    c. p#1,3,5 are maintained in Case#2,3
      z is not root in Case#2,3, no way of violation p#2, only paint RED in Case#3 becomes child of BLACK node no way of violation p#4
    Case#2 -> Case#3 which only violation of p#4 without another violation

    Analysis

    rb_insert running time is O(lg(n))
    rb_insert_fixup running time is O(lg(n))
    Case#1 is executed, z moves up 2 levels up
    Case#2 -> Case#3 -> finish
    Case#3 -> finish

    deletion

    code snap

     1  void rb_delete(T, z) {
     2    y = (l(z) == NIL(T) or r(z) == NIL(T)) ? z : tree_successor(z);
     3    x =  (l(y) != NIL(T)) ? l(y) : r(y);
     4    if (p(y) == NIL(T))
     5      ROOT(T) = x;
     6    else if y == l(p(y))
     7       l(p(y)) = x;
     8    else
     9      r(p(y)) = x;
    10    if (y != z)
    11      k(z) = k(y);
    12      //cp y's data into z
    13    if (c(y) == BLACK)
    14      rb_delete_fixup(T, x);
    15    return y;
    16  }
    
     1  void rb_delete_fixup(T, x) {
     2    while ( x != ROOT(t) and c(x) == BLACK){
     3      if (x == l(p(x)) {
     4        w = r(p(x));
     5        if (c(w) == RED) { //Case#1
     6          c(w) = BLACK;
     7          c(p(w)) = RED;
     8          left_rotate(T, p(x));
     9          w = r(p(x));
    10        }
    11        if (c(l(w) == BLACK and c(r(w)) == BLKACK){ //Case#2
    12          c(w) = RED;
    13          x = p(x);
    14        }
    15        else if (c(r(w)) == BLACK) { Case#3
    16          c(l(w)) = BLACK;
    17          c(w) = RED;
    18          right_rotate(T, w);
    19          w = r(p(x));
    20        }
    21        else {
    22          c(w) = c(p(x));
    23          c(p(x)) = BLACK;
    24          c(r(w)) = BLACK;
    25          left_rorate(T, p(x));
    26          x = ROOT(T);
    27        }
    28    else // "right" <-> "left" do c#3-27
    29    c(x) = BLACK;
    30  }
    

    correctness

    in rb_delete:

    • y is target delete note
    • x is child of y which may break rb tree properties because deleting it's parent y

    rb_delete intents splice y by x, there 3 cases:

    1. y is leaf node, delete it directly
    2. y has on child, splice y with it's child x
    3. y has two children, delete position of it's successor, copy key & data into y 's position.
      leaf
      one child
      two children

    if spliced node y is RED, the rb properties still hold:

    • no black-heights in the tree have changed.
    • no red nodes have been made adjacent.
    • y is not root, root's color remain.

    if spliced node y is BLACK, he rb propeties broken as:

    • y is root, x became new root, violation of p#2
    • if p(y) and x are RED, splice y violates p#4
    • remove BLACK y cause p#5, we set node point by x either double-black (when color is BLACK, black count is 2) or red-black (when color is RED, black count is 1), in that way p#5 still hold, p#1 is broken.

    rb_delete_fixup restores p#1,2,4, maintain p#5
    while loop move the extra black until:

    1. x points to red-black node, set black @ c#29
    2. x points to root, set black @ c#29
    3. suitable rotations and recoloring can be performaced

    there are 4 cases depends on x 's is root and color

    1. if x is new root original is RED/BLACK, if x is not root and original RED: since y is BLACK, c#29 ensure p#1,2,4, remove y BLACK then adding new BLACK p#5 preserves
    2. if x is not root and original BLACK, there are another 4 cases, c#29 ensure no violation of p#1,2, since x is BLACK, in while loop c#2-28 x start points double-black node maintain p#5

    Case#1

    1. after remove back node of y, count(A) = 2 makes p#5 still holds.
    2. subtree α, β, r, g, ε, ξ 's black height is not change before and after transformation.
    3. after transformation, w's color change to BLACK, then transfer to Case#[2-4]
    4. since w is RED, l(w) & r(w) must exists and BLACK for p#5 holds before transformation

    Case#2


    both l(w) & r(w) are BLACK

    1. w must be BLACK, otherwise Case#1 is executed first.
      2.before transformation: x points to double-black count(A) = 2, after transformation: x points either double-black or red-black node, count(A) decrease 1 then count(B) increase 1 letting bh(α) = bh(B) + 2 not changed, in the same way r, g, ε, ξ bh is not change(count(D) decrease 1 then count(B) increase 1) -> p#5 preserve.
    2. if B is RED making D RED exit while loop , c#29 making B BLACK p#4 preserve, if B is BLACK loop continue p#4 holds(transform from Case#1)

    Case#3


    w is BLACK, r(w) is BLACK, l(w) i RED

    1. bh of α, β, r, g, ε, ξ is not changed p#4 holds
    2. since l(w) is RED, c(g) must be BLACK, making C(D) = RED no violation of ##p#4##
    3. after ##Case#3## c(r(w)) is RED then transfer to ##Case#4##

    Case#4


    c(D) is BLACK, c(C) & c(E) is RED

    1. since set x as root then set BLACK, count(A) from 2 to 1, then adding new BLACK B, bh is maintained
    2. α, β, r, g, ε, ξ is not changed, ##p#5## is maintained
    3. make x as root loop terminate, c#29 preserve ##p#1,2##

    Analysis

    1. rb_delete_fixup ##Case#1,3,4## operations of constant time
      2.Case#2 move up by 1 level, and continue when p(x) is RED at most execute lg(n) times.
    2. tree_successor running time is O(lg(n)), overall rb_delete running time is O(lg(n))

    相关文章

      网友评论

          本文标题:3-x 插播rb tree

          本文链接:https://www.haomeiwen.com/subject/yusuvrtx.html