Outline
Purpose
Provide a suite of primitive algorithms on red-black trees.
Classes
- See also
- bslalg_rbtreenode
Description
This component provides a variety of algorithms that operate on nodes forming a red-black binary search tree.
This implementation is adapted from Cormen, Leiserson, Rivest, "Introduction to Algorithms" [MIT Press, 1997].
Summary
The following section provides a short synopsis describing observable behavior of functions supplied in this component. See the full function-level contract for detailed description.
Navigation
The following algorithms search a tree for a value, or iterate over the nodes in a tree:
leftmost Return the leftmost node.
rightmost Return the rightmost node.
next Return the next node in an in-order traversal.
previous Return the previous node in an in-order traversal.
find Find the node with the supplied value.
lowerBound Find the first node not less-than the supplied value.
upperBound Find the first node greater than the supplied value.
Modification
The following algorithms are used in the process of manipulating the structure of a tree:
copyTree Return a deep-copy of the supplied tree.
deleteTree Delete all the nodes of the supplied tree.
findInsertLocation Find the location where a value may be inserted.
findUniqueInsertLocation
Find the location where a unique value may be inserted.
insert Insert the supplied node into the tree.
insertAt Insert the supplied node at the indicated position.
remove Remove the supplied node from the tree.
swap Swap the contents of two trees.
Utility
The following algorithms are typically used when implementing higher-level algorithms (and are not generally used by clients):
isLeftChild Return 'true' if the supplied node is a left child.
isRightChild Return 'true' if the supplied node is a right child.
rotateLeft Perform a counter-clockwise rotation on a node.
rotateRight Perform a clockwise rotation on a node.
Testing
The following algorithms are used for testing and debugging, and generally should not be used in production code:
printTreeStructure Print, to a file, the structure of the supplied tree.
validateRbTree Indicate if a tree is a valid red-black tree.
isWellFormed Indicate if the 'RbTreeAnchor' object is well-formed.
Well-Formed RbTreeAnchor Objects
Many of the algorithms defined in this component operate over a complete tree of nodes, rather than a (possible) subtree referred to through a pointer to a node. These operations refer to a complete tree through a RbTreeAnchor
object, which maintains references to the first, root, and sentinel nodes for the tree, as well as a count of the number of nodes in the tree. RbTreeAnchor
objects supplied to RbTreeUtil
are frequently required to meet a series of constraints that are not enforced by the RbTreeAnchor
type itself. An RbTreeAnchor
object meeting these constraints is said to be "well-formed", and RbTreeUtil::isWellFormed
will return true
for such an object. A RbTreeAnchor
object is considered well-formed if all of the following are true:
- The root node refers to a valid red-black tree (see
validateRbTree
).
- The first node refers to the leftmost node in the tree, or the sentinel node if the tree is empty.
- The node count is the number of nodes in the tree (not counting the sentinel node).
- The sentinel node refers to the root node as its left child, and the root node refers to the sentinel as its parent.
- The root node is either 0 or is colored black.
The manipulation functions of RbTreeUtil
guarantee that these properties are maintained for any supplied tree. Note that RbTreeUtil::isWellFormed
has linear complexity with respect to the number of nodes in the tree, and is typically used for debugging and testing purposes only. Note also that the final condition, that the root node be either 0 or colored black, is not a canonical requirement of a red-black tree but an additional invariant enforced by the methods of RbTreeUtil
to simplify the implementations.
The Sentinel Node
The sentinel node is RbTreeNode
object (unique to an RbTreeAnchor
instance) which does not have a value, and provides a fixed end-point for navigation over the tree (which is distinct from the rightmost
node of that tree). The sentinel node will be returned by next
if the supplied node is the rightmost node in the tree, as well as by search operations when no nodes meet the supplied search-criteria. In addition, the sentinel node may be supplied as a hint
to findInsertLocation
and findUniqueInsertLocation
, as well as supplied to previous
to obtain the rightmost node of a (non-empty) tree.
Usage
This section illustrates intended use of this component.
Example 1: Creating and Using a Tree with RbTreeUtil
This example demonstrates how to create a tree of integers using RbTreeUtil
.
First, we define a type SimpleIntNode
that will represent a nodes in our tree of integer values. SimpleIntNode
contains an int
payload, and inherits from RbTreeNode
, allowing it to be operated on by RbTreeUtil
.
struct SimpleIntNode : public RbTreeNode {
int d_value;
};
Then, we define a comparison function for SimpleIntNode
objects (note that we static-cast RBTreeNode
objects to the actual node type, SimpleIntNode
, for comparison purposes):
struct SimpleIntNodeValueComparator {
bool operator()(const RbTreeNode& lhs, int rhs) const
{
return static_cast<const SimpleIntNode&>(lhs).d_value < rhs;
}
bool operator()(int lhs, const RbTreeNode& rhs) const
{
return lhs < static_cast<const SimpleIntNode&>(rhs).d_value;
}
};
Next, we begin to define the example function that will build a tree of nodes holding integer values:
void createTestTreeExample()
{
Then, within this function, we define a RbTreeAnchor
object that will hold the root, first, last, and sentinel nodes of tree, as well a count of the number of nodes in the tree:
Next, we define an array of 5 SimpleIntNode
objects that we will insert into the tree; in practice, nodes are more often allocated on the heap (see example 2):
const int NUM_NODES = 5;
SimpleIntNode nodes[NUM_NODES];
Then, we assign unique values to each of the nodes
:
for (int i = 0; i < NUM_NODES; ++i) {
nodes[i].d_value = i;
}
Now, for each node in the tree, we use RbTreeUtil
to first find the location at which the node should be inserted, and then insert that node into the tree:
for (int i = 0; i < NUM_NODES; ++i) {
int comparisonResult;
SimpleIntNodeValueComparator comparator;
RbTreeNode *insertLocation = RbTreeUtil::findUniqueInsertLocation(
&comparisonResult,
&tree,
comparator,
nodes[i].d_value);
RbTreeUtil::insertAt(&tree,
insertLocation,
comparisonResult < 0,
&nodes[i]);
}
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804
And verify the resulting tree
holds 5 nodes, and the first node has the value 0:
assert(5 == tree.numNodes());
assert(0 == static_cast<SimpleIntNode *>(tree.firstNode())->d_value);
Finally, we use RbTreeUtil
to iterate through the nodes of tree
, and write the value of each node to the console:
const RbTreeNode *nodeIterator = tree.firstNode();
while (tree.sentinel() != nodeIterator) {
printf("Node value: %d\n",
static_cast<const SimpleIntNode *>(nodeIterator)->d_value);
nodeIterator = RbTreeUtil::next(nodeIterator);
}
}
Notice that each of the RbTreeNode
objects must be static_cast to the derived type, SimpleIntNode
, in order to access their values.
The resulting output is displayed on the console:
Node value: 0
Node value: 1
Node value: 2
Node value: 3
Node value: 4
Example 2: Implementing a Set of Integers
This example demonstrates how to use RbTreeUtil
to implement a simple container holding a set of (unique) integer values as a red-black binary search tree.
Before defining the IntSet
class, we need to define a series of associated helper types:
- The node-type, for the nodes in the tree.
- An iterator, for iterating over nodes in the tree.
- A comparison functor for comparing nodes and values.
- A factory for creating and destroying nodes.
First, we define a type, IntSet_Node
, that will represent the nodes in our tree of integer values; it contains an int
payload, and inherits from RbTreeNode
, allowing it to be operated on by RbTreeUtil
(note that the underscore "_" indicates that this type is a private implementation type of IntSet
, and not for use by clients of IntSet
):
class IntSet_Node : public RbTreeNode {
int d_value;
public:
int& value() { return d_value; }
const int& value() const { return d_value; }
};
Then, we define a iterator over IntSet_Node
objects. We use the next
function of RbTreeUtil
to increment the iterator (note that, for simplicity, this iterator is not a fully STL compliant iterator implementation):
class IntSetConstIterator {
const RbTreeNode *d_node_p;
public:
IntSetConstIterator() : d_node_p(0) {}
IntSetConstIterator(const RbTreeNode *node) : d_node_p(node) {}
Here, we implement the prefix-increment operator using the next
function of 'RbTreeUtil:
IntSetConstIterator& operator++()
{
d_node_p = RbTreeUtil::next(d_node_p);
return *this;
}
const int& operator*() const
{
return static_cast<const IntSet_Node *>(d_node_p)->value();
}
const int *operator->() const
{
return &(static_cast<const IntSet_Node *>(d_node_p)->value());
}
const IntSet_Node *nodePtr() const
{
return static_cast<const IntSet_Node *>(d_node_p);
}
};
const IntSetConstIterator &rhs)
{
return lhs.nodePtr() == rhs.nodePtr();
}
const IntSetConstIterator &rhs)
{
return lhs.nodePtr() != rhs.nodePtr();
}
bool operator!=(const FileCleanerConfiguration &lhs, const FileCleanerConfiguration &rhs)
bool operator==(const FileCleanerConfiguration &lhs, const FileCleanerConfiguration &rhs)
Next, we define a comparison functor for IntSet_Node
objects, which will be supplied to RbTreeUtil
functions that must compare nodes with values – i.e., those with a NODE_VALUE_COMPARATOR
template parameter (e.g., find
and findInsertLocation
):
struct IntSet_NodeValueComparator {
bool operator()(const RbTreeNode& lhs, int rhs) const
{
return static_cast<const IntSet_Node&>(lhs).value() < rhs;
}
bool operator()(int lhs, const RbTreeNode& rhs) const
{
return lhs < static_cast<const IntSet_Node&>(rhs).value();
}
};
Notice that we static-cast RbTreeNode
objects to the actual node type, IntSet_Node
for comparison.
Next, we define a factory for creating and destroying IntSet_Node
objects. This factory provides the operations createNode
and deleteNode
. These operations will be used directly by our container implementation, and they are also required by RbTreeUtil
functions taking a FACTORY
template parameter (e.g., copyTree
and deleteTree
):
class IntSet_NodeFactory {
public:
: d_allocator_p(allocator)
{
}
RbTreeNode *createNode(int value) const
{
IntSet_Node *newNode = new (*d_allocator_p) IntSet_Node;
newNode->value() = value;
return newNode;
}
RbTreeNode *createNode(const RbTreeNode& node) const
{
IntSet_Node *newNode = new (*d_allocator_p) IntSet_Node;
newNode->value() = static_cast<const IntSet_Node&>(node).value();
return newNode;
}
void deleteNode(RbTreeNode *node) const
{
d_allocator_p->
deleteObject(
static_cast<IntSet_Node *
>(node));
}
{
return d_allocator_p;
}
};
Definition bslma_allocator.h:457
void deleteObject(const TYPE *object)
Definition bslma_allocator.h:680
#define BSLS_ASSERT_SAFE(X)
Definition bsls_assert.h:1762
Then, having defined the requisite helper types, we define the public interface for our IntSet
type. Note that for the purposes of illustrating the use of RbTreeUtil
a number of simplifications have been made. For example, this implementation provides only a minimal set of critical operations, and it does not use the empty base-class optimization for the comparator, etc. We define the interface of IntSet
as follows:
class IntSet {
RbTreeAnchor d_tree;
IntSet_NodeValueComparator
d_comparator;
IntSet_NodeFactory d_nodeFactory;
friend bool operator==(const IntSet& lhs, const IntSet& rhs);
public:
typedef IntSetConstIterator const_iterator;
~IntSet();
IntSet& operator=(const IntSet& rhs);
const_iterator insert(int value);
const_iterator erase(const_iterator iterator);
void clear();
void swap(IntSet& other);
const_iterator begin() const;
const_iterator end() const;
const_iterator find(int value) const;
int size() const;
};
bool operator==(const IntSet& lhs, const IntSet& rhs);
bool operator!=(const IntSet& lhs, const IntSet& rhs);
Now, we implement the methods of IntSet
using RbTreeUtil
and the helper types we defined earlier:
: d_tree()
, d_comparator()
, d_nodeFactory(
bslma::Default::allocator(basicAllocator))
{
}
: d_tree()
, d_comparator()
, d_nodeFactory(
bslma::Default::allocator(basicAllocator))
{
if (original.d_tree.rootNode()) {
RbTreeUtil::copyTree(&d_tree, original.d_tree, &d_nodeFactory);
}
}
IntSet::~IntSet()
{
clear();
}
IntSet& IntSet::operator=(const IntSet& rhs)
{
IntSet temp(rhs, d_nodeFactory.allocator());
return *this;
}
void swap(OptionValue &a, OptionValue &b)
Definition balxml_encoderoptions.h:68
Here, we implement insert
by using the RbTreeUtil
algorithms findUniqueInsertLocation
and insertAt
:
IntSet::const_iterator IntSet::insert(int value)
{
int comparisonResult;
RbTreeNode *insertLocation =
RbTreeUtil::findUniqueInsertLocation(&comparisonResult,
&d_tree,
d_comparator,
value);
if (0 == comparisonResult) {
return const_iterator(insertLocation);
}
RbTreeNode *newNode = d_nodeFactory.createNode(value);
RbTreeUtil::insertAt(&d_tree,
insertLocation,
comparisonResult < 0,
newNode);
return const_iterator(newNode);
}
IntSet::const_iterator IntSet::erase(const_iterator iterator)
{
IntSet_Node *node = const_cast<IntSet_Node *>(iterator.nodePtr());
RbTreeNode *next = RbTreeUtil::next(node);
RbTreeUtil::remove(&d_tree, node);
d_nodeFactory.deleteNode(node);
return const_iterator(next);
}
void IntSet::clear()
{
if (d_tree.rootNode()) {
RbTreeUtil::deleteTree(&d_tree, &d_nodeFactory);
}
}
void IntSet::swap(IntSet& other) {
other.d_nodeFactory.allocator());
RbTreeUtil::swap(&d_tree, &other.d_tree);
}
IntSet::const_iterator IntSet::begin() const
{
return const_iterator(d_tree.firstNode());
}
IntSet::const_iterator IntSet::end() const
{
return const_iterator(d_tree.sentinel());
}
IntSet::const_iterator IntSet::find(int value) const
{
return const_iterator(RbTreeUtil::find(d_tree, d_comparator, value));
}
int IntSet::size() const
{
return d_tree.numNodes();
}
Finally, we implement the free operators on IntSet
:
bool operator==(const IntSet& lhs, const IntSet& rhs)
{
lhs.end(),
lhs.size(),
rhs.begin(),
rhs.end(),
rhs.size());
}
bool operator!=(const IntSet& lhs, const IntSet& rhs)
{
return !(lhs == rhs);
}
static bool equal(INPUT_ITER start1, INPUT_ITER end1, INPUT_ITER start2)
Definition bslalg_rangecompare.h:674