A red black tree is a binary tree that is always balanced. To achieve this, the root may
change as you add elements, unlike a binary tree. The 3 RBTree properties are:
Before we move on, let’s look at two quick definitions:
Height of a node
# of edges in longest path from node to leaf
BlackHeight of a node
# number of black child nodes along path to NULL leaf. NULL leaf has BlackHeight 0. For example the black node 7 on the left of the tree has BlackHeight 1.
Notice that in practice all leaves are the same black NULL node and that only the root
node has that same NULL as its parent node. Note that in these notes, we won’t show the
NULL connections so we’d just show this:
Any pseudocode we write will use the fact that all the leaves are the NULL node and so is
the root’s parent.
Here’s some ‘pseudocode’ to do a left rotate for the above diagram.
LeftRotate(Tree t, node x) {
y = x.right; // take y's left
x.right = y.left; // subtree and make
if (y.left != NULL) { // it x's right
y.left.parent = x; // subtree.
}
y.parent = x.parent; // make x's
if (x.parent == NULL) { // parent
t.root = y; // y's new
} else if (x.parent.left == x) { // parent &
x.parent.left = y; // make y the
} else { // parent's
x.parent.right = y; // correct
} // child.
y.left = x; // make x y's left child
x.parent = y; // make y x's parent
}
For a node \(x\) with height \(h\), \(blackheight(x) \ge {h \over 2}\).
Clearly true if no consecutive reds are allowed but consecutive blacks are.
A node \(x\) must have at least \(2^{blackheight(x)} - 1\) descendants. (not including NULLs)
The rigorous proof is pretty annoying but the intuition is pretty easy. If we had a perfectly
balanced and completely full binary tree, the root \(x\) of any given subtree would clearly
have at least \(2^{height(x)} - 1\) descendants. Well, an RBTree is always full and perfectly
balanced with respect to blackheight (by property 3) – and this is true for all subtrees
rooted at any node in the RBTree. So the number of black descendants of any given node \(x\)
in the RBTree is therefore always at least \(2^{blackheight(x)} - 1\). So, that fact paired
with the fact that the blackheight of a node doesn’t account for all the possible red nodes
that can be sandwiched between all the black nodes means that for any node \(x\) in the RBTree,
\(x\) always has at least \(2^{blackheight(x)} - 1\) descendants.
An RBTree with \(n\) descendants has height at most \(2 log_2(n + 1)\)
Let \(n\) be the number of descendants of an RBTree, \(h\) be its height and let \(bh\) be its
blackheight. We just showed that \(n \ge 2^{blackheight(x)} - 1\). By simple algebra we can
rearrange this to be \(log_2(n+1) \ge bh\). We also saw that \(bh \ge {h \over 2}\). So,
\[
log_2(n+1) \ge bh \ge {h \over 2}\ \implies\ h \le 2 log_2(n+1)
\] Realize that this statement is proof the RBTree is balanced. We’ve shown that for the \(n\)
elements and height \(h\) of an RBTree, \(h \in O(log(n))\). This is the entire goal of RBTrees
in the first place.
Insertion into an RBTree basically consists of two steps:
Here’s the pseudocode for the first step.
Algorithm RBT Insertion(RBTree T, node n):
Set n's color to red and both children to NULL
Let c be the root node
Let p be NULL
If c == NULL // handle the case of
T.root = n // n being added to
n.color = black // an empty RBTree
return
Endif
While c is not NULL // find the spot to
p = c // put the new node
If n <= c // just like you
c = c.left // would for a
Else // regular BSTree
c = c.right
Endif
Endwhile
n.p = p // set the parent-child
If n < p // relationship between
p.left = n // n and its new parent
Else // appropriately
p.right = n
Endif
call RBT Insertion Fixup(T,n)
Endalgorithm
So how does the second step, the fixup step, work? Well, it turns out there are really
just three possible cases where the insertion results in a violation of RBTree
properties. All three of those cases are defined in the picture below and are even
accompanied by visual representations of typical examples.
Before we inspect these cases and their fixes one-by-one, let’s notice one thing first:
In all three cases, the issue is that the new node was added as a child of a red node.
Realize that none of the RBTree properties are violated if the new red node is added
as a child of a black node. Now that that’s out of the way, let’s take a closer look
at each case.
Okay, here’s the pseudocode to achieve that:
Algorithm Insertion Fixup(RBTree T, node n):
If n.parent.color == red
//if n's parent is a left child
If n.parent == n.parent.parent.left
node uncle = n.parent.parent.right // Case 1:
If uncle.color == red // Toggle color of
Toggle n.parent.color // uncle, parent and
Toggle uncle.color // grandparent.
Toggle n.parent.parent.color // Recursive call
Recursive call on n.parent.parent // on grandparent.
Return;
Else
If n == n.parent.right // Case 2:
n = n.parent // Right-rotate around n's
Right-rotate on n // parent and recursive
Recursive call on n // call on n's old parent.
Return
Else
Right-rotate on n.parent.parent // Case 3:
Toggle n.parent.color // Toggle color of
Toggle n.parent.right.color // n's parent and
Return // new sibling.
Endif // End of Alg.
Endif
//if n's parent is a right child
Else
// same as before but
// with "left" and "right"
// exchanged
Endif
Endif
Endalgorithm
If you really must know, you can find out how it works here. I hope
you found this document helpful.