RedBlackTreeSetOrMapImpl
RedBlackTreeSetOrMapImpl is a final class template
defined in Fw/DataStructures.
It represents a set or map implementation based on a red-black tree.
Internally it maintains an ExternalArray
of tree nodes and an ExternalStack of
indices pointing into the array.
The implementation uses the algorithm described here:
https://en.wikipedia.org/wiki/Red%E2%80%93black_tree.
1. Red-Black Trees
A red-black tree is a binary search tree (BST) T together with a red-black coloring, i.e., an assignment of a color (red or black) to each node of T. A red-black tree T is valid if it satisfies certain validity constraints, which we state below. The validity constraints ensure that the tree is balanced, i.e., that no path from the root of the tree to a leaf is too much longer than any of the other paths. Therefore the validity constraints ensure that search in the BST is fast (order log n).
1.1. Binary Trees
A binary tree T is a data structure defined as follows:
-
T has a set S of nodes.
-
If S is nonempty, then
-
There is one node R called the root of T.
-
For each node N
-
There may be zero, one, or two nodes designated as the children of N.
-
Each child of N is designated the left or right child of N. If N has two children, then there is exactly one left child and one right child.
-
-
Each node except R is the child of exactly one node. (Equivalently, each node except R has exactly one parent.)
-
R is not the child of any node. (Equivalently, R has no parent.)
-
No path that starts at a node and follows child links contains a cycle.
-
Here is an example of a binary tree:
In this diagram we adopt the following conventions, which we will use throughout this document:
-
The ovals are the nodes of the tree.
-
A solid arrow denotes a parent-child relationship between nodes. Each arrow goes from parent to child.
-
The left child of a node N, if it exists, is drawn to the left of and below N. The right child of N, if it exists, is drawn to the right and below.
Let T be a binary tree, and let N be a node of T. We say that N is a leaf node of T if N has no children. For example, the tree shown above has a root, two leaves, and no other nodes.
1.2. Binary Search Trees
A binary search tree (BST) is a binary tree T with the following additional properties:
-
Each node N of T stores a key K. All the keys are values of the same totally-ordered type, e.g., integers, strings, etc.
-
Each node N of T satisfies the following properties:
-
If N has a left child LC, then any key stored in LC or in any node reachable by following child links from LC compares less than the key stored in N.
-
If N has a right child RC, then the key stored in RC or in any node reachable by following child links from RC compares greater than the key stored in N.
-
Here is an example of a BST:
In this diagram we adopt the following conventions, which we will use throughout this document:
-
The labels in the nodes are symbolic names for the keys stored in the nodes.
-
A lexically prior name stores a prior key. For example, we could have K1 = 1, K2 = 2, K3 = 3; or K1 =
"a", K2 ="b", K3 ="c"; etc.
1.3. Red-Black Colorings
A red-black tree is a BST T together with a red-black coloring, i.e., an assignment of a color (red or black) to each node of T. For example, here is a red-black tree:
The color of a node in the diagram is its color in the red-black coloring.
Certain red-black trees are considered valid. In the rest of this section, we define the validity constraints. The standard BST insert and remove operations can violate the validity constraints. Therefore, when inserting or removing a node, we must perform a rebalancing, i.e., a transformation that rearranges links and recolors nodes to maintain the BST property and to produce a valid coloring.
1.3.1. Leaf-Augmented Trees
In order to state the validity constraints for a red-black tree, we need to define the concept of a leaf-augmented tree. Let T be a red-black tree. We define the leaf-augmented tree T' corresponding to T as follows:
-
If T is empty, then T' consists of a single black node.
-
Otherwise, T' is the red-black colored binary tree that results from (1) adding a new black leaf node as a left child of every node of T that has no left child and (2) adding a new black leaf node as a right child of every node of T that has no right child.
For example, here is the leaf-augmented tree corresponding to the tree shown above:
Notice that a leaf-augmented tree has the following properties:
-
Every leaf node is black.
-
Every node either is a leaf node or has exactly two children.
1.3.2. Valid Colorings
A red-black tree T is valid if its coloring satisfies the following constraints:
-
RBT1: No red node of T has a red child.
-
RBT2: Let T' be the leaf-augmented tree constructed from T as described in the previous section. For each node N in T', every path that follows child links from N to a leaf node must pass through the same number of black nodes.
For example, the first tree shown in the section Red-Black Colorings is a valid red-black tree. Notice that RBT2 is satisfied at each node. For example, at the root, each path from the root to a leaf in the leaf-augmented tree passes through two black nodes.
This tree is not a valid red-black tree because RBT2 is not satisfied:
Here is the corresponding leaf-augmented tree:
Note that in the leaf-augmented tree, the path from K2 to either child of K1 goes through two black nodes, while the path from K2 to its right child goes through one black node.
1.3.3. Black Height
When describing the insert and remove algorithms, it will be useful to have the notion of the black height of a path, a node, a tree, or a forest of trees.
-
Paths: Let P be a path from a node of a red-black tree to a leaf node. The black height of P is the number of black nodes in P.
-
Nodes: Let N be a node of a red-black tree. If every path P from N to a leaf under N has the same black height n, then the black height of N is defined and is equal to n. Otherwise the black height of N is not defined.
-
Trees: Let T be a red-black tree. Let T' be the corresponding leaf-augmented tree. If the root of T' has a defined black height n, then the black height of T is defined and is equal to n. Otherwise the black height of T is not defined.
-
Forests: Let F be a forest of red-black trees. If every tree T in F has a defined black height, and the black heights all have the same value n, then the black height of F is defined and is equal to n. Otherwise the black height of F is not defined.
Let T be a red-black tree, and note the following:
-
T satisfies RBT2 if and only if it has a defined black height.
-
T has a defined black height if and only if each of the child subtrees of the root of T has a defined black height, and the two black heights are the same.
1.3.4. Representing Forests
Let F be a forest of red-black trees with a defined black height. To represent such a forest, we will write a dotted box, and we may write a property of the forest in the box. For example, we may represent the first tree shown in the section Red-Black Colorings as follows:
Similarly, we may represent the tree shown in the section Valid Colorings as follows:
Then it is easy to see that the first tree has black height 2, and the second tree has no defined black height.
An arrow pointing to an empty forest means the same thing as no arrow.
1.3.5. Local Constraint Violations
To describe the algorithms, it will be useful to have a precise way to state where a constraint violation occurs in an invalid red-black tree. Therefore we make the following definitions for a red-black tree T:
-
If any node N is red and has a red child C, then we say that T has a red child violation from N to C.
-
If the subtree rooted at any node N does not have a defined black height, then we say that T has a black height violation at N.
From the definitions, it is clear that T is valid if and only if it has no violation at any node.
2. Template Parameters
RedBlackTreeSetOrMapImpl has the following template parameters.
| Kind | Name | Purpose |
|---|---|---|
typename |
KE |
The type of a key in a map or the element of a set |
typename |
VN |
The type of a value in a map or Nil for set |
3. Types
3.1. Node
Node is a struct defined as a public member of RedBlackTreeSetOrMapImpl.
It represents a node of the red-black tree.
3.1.1. Public Types
Node defines the following public types.
| Name | Definition | Purpose |
|---|---|---|
Color |
An enumeration with values BLACK and RED |
A node color |
Direction |
An enumeration with values LEFT and RIGHT |
A tree direction |
Entry |
An alias for SetOrMapImplEntry<KE, VN> |
A set or map entry in a tree node. |
Index |
FwSizeType |
An array index representing a tree node |
3.1.2. Public Constants
Node defines the following constants.
| Name | Type | Purpose | Value |
|---|---|---|---|
NONE |
Index |
An out-of-bounds index value corresponding to no node | std::numeric_limits<Index>::max() |
3.1.3. Public Member Variables
Node has the following public member variables.
| Name | Type | Purpose | Default Value |
|---|---|---|---|
parent |
Node::Index |
The index of the parent of this node | Node::NONE |
left |
Node::Index |
The index of the left child of this node | Node::NONE |
right |
Node::Index |
The index of the right child of this node | Node::NONE |
color |
Color |
The color of this node | Color::BLACK |
entry |
Entry |
The set or map entry stored in this node | C++ default initialization |
3.1.4. Public Member Functions
3.1.4.1. getChild
Overview:
Gets the child of this in direction direction.
Algorithm:
Return (direction == LEFT) ? left : right.
3.1.4.2. setChild
Overview:
Sets the child of this in direction direction.
Algorithm:
(direction == LEFT) ? (this.left = node) : (this.right = node).
3.1.5. Public Static Methods
3.1.5.1. getOppositeDirection
Overview: Returns the opposite direction.
Algorithm:
Return (direction == LEFT) ? RIGHT : LEFT.
3.2. Nodes and FreeNodes
RedBlackTreeSetOrMapImpl defines the following public type aliases.
| Name | Definition | Purpose |
|---|---|---|
Nodes |
Alias for ExternalArray<Node> |
The type of the array for storing the tree nodes |
FreeNodes |
Alias for ExternalStack<Node::Index> |
The type of the stack of indices of free nodes. |
3.3. ConstIterator
ConstIterator is a public inner class of RedBlackTreeSetOrMapImpl.
It provides non-modifying iteration over the elements of a RedBlackTreeSetOrMapImpl
instance.
It is a base class of SetOrMapImplConstIterator<KE,
VN>.
State:
ConstIterator maintains the following state:
-
A pointer to a
RedBlackTreeSetOrMapImplinstance. -
A
Node::Indexvalue.
Operations:
To create the iterator in the begin state, we use
getOuterNodeUnder to traverse the
tree to the leftmost node under the root, and we set the node index to point to
that node.
To set the iterator to the end state, we set the node index
to NONE.
Incrementing the iterator works as follows:
-
If the node index is
NONE, then do nothing. -
Otherwise if the current node has a non-null right child, then use
getOuterNodeUnderto traverse to the leftmost element under the right child. Set the node index to point to this node. -
Otherwise traverse the tree upwards, stopping when we have traversed through a left child or the node index is
NONE.
4. Private Member Variables
RedBlackTreeSetOrMapImpl has the following private member variables.
| Name | Type | Purpose | Default Value |
|---|---|---|---|
m_nodes |
Nodes |
The array for storing the tree nodes | C++ default initialization |
m_freeNodes |
FreeNodes |
The stack of indices of free nodes. The indices point into m_nodes. |
C++ default initialization |
m_root |
Node::Index |
The index of the root node | Node::NONE |
classDiagram
RedBlackTreeSetOrMapImpl *-- ExternalArray
RedBlackTreeSetOrMapImpl *-- ExternalStack
ExternalArray *-- "1..*" Node
5. Public Constructors and Destructors
5.1. Zero-Argument Constructor
Initialize each member variable with its default value.
5.2. Constructor Providing Typed Backing Storage
Each of nodes and freeNodes must point to at least capacity items.
Call setStorage(nodes, freeNodes, capacity).
5.3. Constructor Providing Untyped Backing Storage
data must be aligned according to
getByteArrayAlignment() and must
contain at least getByteArraySize(capacity) bytes.
Call setStorage(data, capacity).
5.4. Copy Constructor
Set *this = impl.
5.5. Destructor
Defined as = default.
6. Public Member Functions
6.1. operator=
-
If
&impl != this-
Set
m_nodes = impl.m_nodes. -
Set
m_freeNodes = impl.m_freeNodes. -
Set
m_root = impl.m_root.
-
-
Return
*this.
6.2. begin
Return ConstIterator(*this).
6.3. clear
-
Set
m_root = NONE. -
Call
m_freeNodes.clear(). -
Let
capacity = getCapacity(). -
For each
iin the range[0, capacity)-
Let
status = m_freeNodes.push(capacity - i - 1). -
Assert
status == SUCCESS.
-
6.4. end
-
Set
it = begin(). -
Call
it.setToEnd(). -
Return
it.
6.5. find
-
Set
node = NONE. -
Set
direction = LEFT. -
Let
status = findNode(keyOrElement, node, direction). -
If
status == SUCCESS- Set
valueOrNil = m_nodes[node].entry.getValue().
- Set
-
Return
status.
6.6. getCapacity
Return m_nodes.getSize().
6.7. getSize
-
Let
capacity = getCapacity(). -
Let
freeNodesSize = m_freeNodes.getSize(). -
Assert
freeNodesSize <= capacity. -
Return
capacity - freeNodesSize.
6.8. insert
-
Set
node = NONE. -
Set
direction = LEFT. -
Set
status = FAILURE. -
Let
findStatus = findNode(keyOrElement, node, direction). -
If
findStatus == SUCCESS-
Call
m_nodes[node].setValue(valueOrNil). -
Set
status = SUCCESS.
-
-
Otherwise
-
Let
parent = node. -
Let
status = m_freeNodes.pop(node). -
If
status == SUCCESS-
Set
m_nodes[node] = Node(). -
Call
m_nodes[node].entry.setKey(keyOrElement). -
Call
m_nodes[node].entry.setValue(valueOrNil). -
Call
insertNode(node, parent, direction).
-
-
Return
status.
6.9. remove
-
Set
node = NONE. -
Set
direction = LEFT. -
Let
status = findNode(keyOrElement, node, direction). -
If
status == SUCCESS-
Set
valueOrNil = m_nodes[node].entry.getValue(). -
Set
removedNode = NONE. -
Call
removeNode(node, removedNode). -
Let
pushStatus = m_freeNodes.push(removedNode). -
Assert
pushStatus == SUCCESS.
-
-
Return
status.
6.10. setStorage (Typed Data)
Each of nodes and freeNodes must point to at least capacity items.
-
Call
m_nodes.setStorage(nodes, capacity). -
Call
m_freeNodes.setStorage(freeNodes, capacity). -
Call
this->clear().
6.11. setStorage (Untyped Data)
data must be aligned according to
getByteArrayAlignment() and must
contain at least getByteArraySize(size) bytes.
-
Call
m_nodes.setStorage(data, capacity). -
Let
nodesSize = Nodes::getByteArraySize(). -
Let
freeNodesOffsetbe the smallest integer greater than or equal tonodesSizethat is aligned forFreeNodes::getByteArrayAlignment(). -
Let
freeNodesSize = FreeNodes::getByteArraySize(capacity). -
Assert that
freeNodesOffset + freeNodesSize <= data.size. -
Let
freeNodesData = ByteArray(&data.bytes[freeNodesOffset], freeNodesSize). -
Call
m_freeNodes.setStorage(freeNodesData, capacity). -
Call
this->clear().
7. Public Static Functions
7.1. getByteArrayAlignment
Return Nodes::getByteArrayAlignment().
7.2. getByteArraySize
-
Let
nodesSize = Nodes::getByteArraySize(capacity). -
Let
freeNodesAlignment = FreeNodes::getByteArrayAlignment(). -
Let
freeNodesSize = FreeNodes::getByteArraySize(capacity). -
Return
nodesSize + freeNodesAlignment + freeNodesSize.
8. Private Helper Functions
8.1. findNode
Overview:
This function tries to find a node whose key or element ke matches
keyOrElement.
On return from the function:
-
If the function found such a node N, then the return value is
SUCCESS, andnodestores the index of N. -
Otherwise
-
The return value is
FAILURE. -
If the tree is empty, then
nodeholdsNONE. -
Otherwise
nodestores the index of the node N containing theNONEchild where ke should be inserted, anddirectionstores the direction of the child in N (left or right).
-
Algorithm:
-
Set
result = FAILURE. -
Set
parent = NONE. -
Set
child = m_root. -
Let
capacity = getCapacity(). -
Set
done = (capacity == 0). -
In a for loop bounded by
capacity:-
If
child == NONEthen setnode = parent, setdone = true, and break out of the loop. -
Compare
keyOrElementto the key or element in the entry stored inm_nodes[child]. -
If the comparison result is equality, then set
done = true, setresult = SUCCESS, setnode = child, and break out of the loop. -
Otherwise if the comparison result is less than set
direction = LEFT, setparent = child, and setchild = m_nodes[parent].left. -
Otherwise set
direction = RIGHT, setparent = child, and setchild = m_nodes[parent].right.
-
-
Assert
done == true. -
Return
result.
8.2. getDirectionFromParent
Overview: Get the direction from the parent, i.e.,
the direction (left or right) to follow from the parent
of node to get to node.
node must not be NONE.
The parent of node must not be NONE.
Algorithm:
-
Let
parent = m_nodes[node].parent. -
Let
parentRight = m_nodes[parent].right. -
Return
node == parentRight ? RIGHT : LEFT.
8.3. getNodeColor
Return index == NONE ? BLACK : m_nodes[index].color.
8.4. getOuterNodeUnder
Overview:
Get the outer node under node in the specified direction.
If node has no child in that direction, then the result is node.
Algorithm:
-
Set
child = (node != NONE) ? m_nodes[node].getChild(direction) : NONE. -
Set
done = (node == NONE). -
In a for loop bounded by
getCapacity()-
If
child == NONEthen setdone = trueand break out of the loop. -
Set
node = child. -
Set
child = m_nodes[child].getChild(direction).
-
-
Assert
done == true. -
Return
node.
8.5. getPredecessorOfNone
Overview:
This function gets the predecessor of a NONE node, specified as
(1) a node, which is either NONE itself or a node with a NONE child;
and (2) a direction.
The predecessor of a node is the predecessor in the inorder traversal of the
tree.
If node is NONE, then the function returns NONE and ignores direction.
Otherwise, the function returns the predecessor of the
the child of node in the direction direction, assuming that the
child is NONE.
Algorithm:
-
Set
result = node. -
If
node != NONE-
Set
parent = m_nodes[node].parent. -
If
parent == NONEanddirection == LEFTthen setresult = NONE. -
If
parent != NONEandm_nodes[parent].direction != directionthen setresult = parent.
-
-
Return
result.
8.6. insertNode
Overview:
This function inserts node into the tree as a left or right
child of parent, according to direction.
It rebalances the tree as needed to maintain the red-black
invariant.
It is permissible for parent to be NONE.
In this case we are inserting at the root of the tree, and direction is
ignored.
It is not permissible for node to be NONE.
Algorithm:
-
We assume (1) that the tree is a red-black tree, (2) that
parentis none or the child ofparentin the directiondirectionisNONE, and (3) that both children ofnodeareNONE. -
Set
m_nodes[node].color = RED. -
Set
m_nodes[node].parent = parent. -
If
parent == NONEthen setm_root = node. The tree was empty, and now it consists of a single red node. -
Otherwise
-
Call
m_nodes[parent].setChild(direction, node). -
Let
capacity = getCapacity(). -
Set
done = (capacity == 0). -
In a for loop bounded by
capacity:-
The following invariants hold: (1)
nodeis colored red; (2) there may be a red child violation fromparenttonode; and (3) there are no other violations at any nodes. -
If
getNodeColor(parent) == BLACKthen setdone = trueand break out of the loop. There is no red child violation atparent, becauseparentis black. -
Set
grandparent = m_nodes[parent].parent. -
If
grandparent == NONEthen-
Set
m_nodes[parent].color = BLACK. This step removes the red child violation atparent, and it preserves all other invariants. -
Set
done = true.
-
-
Otherwise
-
Let
parentDirection = getDirectionFromParent(parent). -
Let
parentOppositeDirection = Node::getOppositeDirection(parentDirection). -
Let
uncle = m_nodes[grandparent].getChild(parentOppositeDirection). -
If
getNodeColor(uncle) == BLACKthen-
If
m_nodes[parent].getChild(parentOppositeDirection) == node- The subtree rooted at
grandparenthas the following shape, assuming thatparentDirectionisRIGHT. There is a red child violation fromparenttonode. There are no other violations at any nodes.
-
Call
rotateSubtree(parent, parentDirection). -
Set
parent = m_nodes[grandparent].getChild(parentDirection).
- The subtree rooted at
-
The subtree rooted at
grandparenthas the following shape, assuming thatparentDirectionisRIGHT. There is a red child violation fromparentto K4. There are no other violations at any nodes.
-
Call
rotateSubtree(grandparent, parentOppositeDirection). -
Set
m_nodes[parent].color = BLACK. -
Set
m_nodes[grandparent].color = RED. -
Set
done = true. -
The subtree has the following shape:
-
-
Otherwise
-
The subtree rooted at
grandparenthas one of four shapes, one of which is shown below. Each of the arrows to red nodes may point the other way. There is a red child violation fromparentto K4. There are no other violations at any nodes. -
Set
m_nodes[parent].color = BLACK. -
Set
m_nodes[uncle].color = BLACK. -
Set
m_nodes[grandparent].color = RED. -
Set
node = grandparent. -
Set
parent = m_nodes[node].parent. -
Here the tree shown above is transformed into the following shape:
- If
parent == NONEthen setdone = true.
-
-
-
If
donethen break out of the loop.
-
-
Assert
done.
-
-
Here the tree is a red-black tree.
8.7. removeNode
Overview:
This function removes a node of the tree.
On entry, node stores the key and value to be removed.
It must not be NONE.
On return, removedNode stores the node that was actually removed.
Algorithm:
-
If
m_nodes[node].left != NONEandm_nodes[node].right != NONEthen callremoveNodeWithTwoChildren(node, removedNode). -
Otherwise
-
Call
removeNodeWithAtMostOneChild(node). -
Set
removedNode = node.
8.8. removeBlackLeafNode
Overview:
This function removes a node that is colored black and
is a leaf node (i.e., it has no children) and is not the root.
node stores the node to remove.
It must not be NONE.
Algorithm:
-
We assume that the tree is a red-black tree, that
nodeis colored black, thatnodeis a leaf node, and thatnodeis not the root. -
Set
parent = m_nodes[node].parent. Sincenodeis not the root,parentis notNONE. -
Set
direction = getDirectionFromParent(node). -
Set
oppositeDirection = Node::getOppositeDirection(direction). -
The leaf-augmented subtree rooted at
parenthas this shape, assumingdirection == RIGHT. We use gray to represent the unknown color (red or black) ofparent. The black heights are the black heights of the original tree.
-
Call
m_nodes[parent].setChild(direction, NONE). This step performs the deletion. -
The leaf-augmented subtree rooted at
parentlooks like this, assumingdirection == RIGHT:
Constraint RBT2 is violated, because the left subtree
of parent has black height 2, and the right subtree has black height 1.
Therefore there is a black height violation at parent and
at every node in the path from the root to parent.
To comply with RBT2, we must perform rebalancing.
-
Let
capacity = getCapacity(). -
Set
done = (capacity == 0). -
For each
iin the range[0, capacity)- Constraint RBT1 is satisfied. Constraint RBT2 is violated because
the leaf-augmented subtree rooted at
parenthas this shape, assumingdirection == RIGHT. i is the loop index. Note that this diagram agrees with the previous one when i = 0.
Constraint RBT2 would be satisfied if the child of
parentin the directiondirectionwere replaced with a red-black tree of black height i + 2.-
Set
sibling = m_nodes[parent].getChild(oppositeDirection). -
Set
closeNephew = m_nodes[sibling].getChild(direction). -
Set
distantNephew = m_nodes[sibling].getChild(oppositeDirection). -
The leaf-augmented subtree rooted at
parenthas this shape (direction ==RIGHT). IfdistantNepheworcloseNepheware leaves in the leaf-augmented tree, then K1 and K4 are names for the nodes; they are not keys in the original tree.
-
If
m_nodes[sibling].color == RED- The leaf-augmented subtree rooted at
parenthas this shape.
-
Call
rotateSubtree(parent, direction). -
Set
m_nodes[parent].color = RED. -
Set
m_nodes[sibling].color = BLACK. -
Set
sibling = closeNephew. -
Set
closeNephew = m_nodes[sibling].getChild(direction). -
Set
distantNephew = m_nodes[sibling].getChild(oppositeDirection). -
The subtree has this shape:
-
If
getNodeColor(distantNephew) == RED- The subtree has this shape:
-
Call
removeBlackLeafNodeHelper2(parent, sibling, distantNephew, direction). -
The subtree has this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
Otherwise if
getNodeColor(closeNephew) == RED- The subtree has this shape:
-
Call
removeBlackLeafNodeHelper1(closeNephew, oppositeDirection, sibling, distantNephew). -
The subtree has this shape:
-
Call
removeBlackLeafNodeHelper2(parent, sibling, distantNephew, direction). -
The subtree has this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
Otherwise
-
Set
m_nodes[sibling].color = RED. -
Set
m_nodes[parent].color = BLACK. -
The subtree has this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
- The leaf-augmented subtree rooted at
-
Otherwise if
getNodeColor(distantNephew) == RED- The subtree has this shape:
-
Call
removeBlackLeafNodeHelper2(parent, sibling, distantNephew, direction). -
The subtree has this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
Otherwise if
getNodeColor(closeNephew) == RED- The subtree has this shape:
-
Call
removeBlackLeafNodeHelper1(closeNephew, oppositeDirection, sibling, distantNephew). -
The subtree has this shape:
-
Call
removeBlackLeafNodeHelper2(parent, sibling, distantNephew, direction). -
The subtree has this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
Otherwise if
getNodeColor(parent) == RED-
Set
m_nodes[sibling].color = RED. -
Set
m_nodes[parent].color = BLACK. -
The leaf-augmented subtree rooted at
parenthas this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
-
Otherwise
- The leaf-augmented subtree rooted at
parenthas this shape.
-
Set
m_nodes[sibling].color = RED. -
Set
node = parent. -
Set
parent = m_nodes[node].parent. -
If
parent == NONE- The entire leaf-augmented tree has this shape. The entire tree is a valid red-black tree.
- Set
done = trueand break out of the loop.
-
Otherwise
-
Set
direction = getDirectionFromParent(node). -
Set
oppositeDirection = Node::getOppositeDirection(direction). -
The leaf-augmented subtree rooted at
parenthas this shape, assuming that direction == RIGHT:
RBT2 is not satisfied because the left subtree of
parenthas black height i + 3, and the right subtree has black height i + 2. So we need at least one more loop iteration. After incrementing i, the invariant at the top of the loop is satisfied. -
- The leaf-augmented subtree rooted at
- Constraint RBT1 is satisfied. Constraint RBT2 is violated because
the leaf-augmented subtree rooted at
-
Assert
done == true. -
The tree is a valid red-black tree.
8.9. removeBlackLeafNodeHelper1
void removeBlackLeafNodeHelper1(
Node::Index closeNephew,
Direction oppositeDirection,
Node::Index& sibling,
Node::Index& distantNephew
)
Overview:
This is a helper function for removeBlackLeafNode.
sibling and distantNephew are in-out parameters (they are both read and
written).
Algorithm:
-
Call
rotateSubtree(sibling, oppositeDirection). -
Set
m_nodes[sibling].color = RED. -
Set
m_nodes[closeNephew].color = BLACK. -
Set
distantNephew = sibling. -
Set
sibling = closeNephew.
8.10. removeBlackLeafNodeHelper2
void removeBlackLeafNodeHelper2(
Node::Index parent,
Node::Index sibling,
Node::Index distantNephew,
Direction direction
)
Overview:
This is a helper function for removeBlackLeafNode.
Algorithm:
-
Call
rotateSubtree(parent, direction). -
Set
m_nodes[sibling].color = m_nodes[parent].color. -
Set
m_nodes[parent].color = Color::BLACK. -
Set
m_nodes[distantNephew].color = Color::BLACK.
8.11. removeNodeWithAtMostOneChild
Overview:
This function removes a node of the tree with at most one child.
On entry, node stores the node to be removed.
It must not be NONE.
Algorithm:
-
If
m_nodes[node].left != NONEthen callremoveNodeWithOneChild(node, LEFT). -
Otherwise if
m_nodes[node].right != NONEthen callremoveNodeWithOneChild(node, RIGHT). -
Otherwise if
node == m_rootthen setm_root = NONE. -
Otherwise if
m_nodes[node].color == REDthen callremoveRedLeafNode(node). -
Otherwise call
removeBlackLeafNode(node).
8.12. removeNodeWithOneChild
Overview:
This function removes a node of the tree with exactly one
child.
node stores the node to remove.
It must not be NONE.
direction stores the direction of the child.
Algorithm:
-
Assert
m_nodes[node].color == BLACK. -
Let
parent = m_nodes[node].parent. -
Let
child = m_nodes[node].getChild(direction). -
Assert
m_nodes[child].color == RED. -
If
parent == NONEthen setm_root = child. -
Otherwise
-
Let
direction = getDirectionFromParent(node). -
Call
m_nodes[parent].setChild(direction, child).
-
-
Set
m_nodes[child].parent = parent. -
Set
m_nodes[child].color = BLACK.
8.13. removeNodeWithTwoChildren
Overview:
This function removes a node of the tree that has two children.
On entry, node stores the key and value to be removed.
It must not be NONE, and it must have two children.
On return, removedNode stores the node that was actually removed.
Algorithm:
-
Set
removedNodeto the successor ofnode. This is the leftmost node under the right child ofnode. UsegetOuterNodeUnderto compute it. -
Set
m_nodes[node].entry = m_nodes[removedNode].entry. -
Call
removeNodeWithAtMostOneChild(removedNode).
8.14. removeRedLeafNode
Overview:
This function removes a node that is colored red and
is a leaf node (i.e., it has no children) and is not the root.
node stores the node to remove.
It must not be NONE.
Algorithm:
-
Assert
m_nodes[node].color == RED. -
Assert
node != m_root. -
Let
parent = m_nodes[node].parent. -
Let
direction = getDirectionFromParent(node). -
Call
m_nodes[parent].setChild(direction, NONE).
8.15. rotateSubtree
Overview:
This function performs a left or right rotation on the subtree
whose root is node.
The following invariants must hold on entry to this function,
or an assertion failure will occur:
-
nodemust not beNONE. -
The child of
nodein the direction oppositedirectionmust not beNONE.
Algorithm:
-
We assume that the tree is a BST.
-
Let
parent = m_nodes[node].parent. -
Let
oppositeDirection = Node::getOppositeDirection(direction). -
Let
newRoot = m_nodes[node].getChild(oppositeDirection)). -
Let
newChild = m_nodes[newRoot].getChild(direction). -
Call
m_nodes[node].setChild(oppositeDirection, newChild). -
If
newChild != NONEthen setm_nodes[newChild].parent = node. -
Call
m_nodes[newRoot].setChild(direction, node). -
Set
m_nodes[newRoot].parent = parent. -
If
parent != NONEthen-
Let
parentDirection = getDirectionFromParent(node). -
Call
m_nodes[parent].setChild(parentDirection, newRoot).
-
-
Otherwise set
m_root = newRoot. -
Set
m_nodes[node].parent = newRoot. -
The tree is a BST.
Example:
Here is an example of applying rotateSubtree to do a left rotation.
Before applying the function, the tree looks like this:
After applying the function, the tree looks like this:
Notice that the rotation preserves the BST property.