Implementing Map and Set Using a Single Red-Black Tree Template
Encapsulating containers like map and set with a single Red-Black Tree (RBTree) template presents several design considerations. The key goals include implementing iterators, supporting increment/decrement operations, and enforcing container-specific constraints: preventing value modification in set and key modification in map.
Adapting a Single Tree Structure
A unified RBTree template must handle differing data requirements:
- For
map, data is apair<const K, V>. Theconst Kensures the key is immutable. - For
set, the key and data type are identical (K). The template parametersKandTboth becomeK.
A functor KeyOfT extracts the comparison key from the stored data. For map, it returns the first member of the pair; for set, it returns the value itself.
The Insert method returns a pair<Node*, bool>. Using a raw node pointer resolves potential type conflicts when the same RBTree must return different iterator types (iterator vs const_iterator) to its encapsulating containers.
Red-Black Tree Iterator
The iterator provides access to nodes in sorted order. The core structure includes dereferencing operators and comparison logic.
template<typename DataType>
struct TreeIterator {
typedef RBTreeNode<DataType> TreeNode;
typedef TreeIterator<DataType> IteratorSelf;
TreeNode* nodePtr;
TreeIterator(TreeNode* node) : nodePtr(node) {}
DataType& operator*() { return nodePtr->nodeData; }
DataType* operator->() { return &(nodePtr->nodeData); }
bool operator!=(const IteratorSelf& other) const {
return nodePtr != other.nodePtr;
}
bool operator==(const IteratorSelf& other) const {
return nodePtr == other.nodePtr;
}
Implementing Pre-increment (++)
The iterator advances to the next node in an in-order traversal.
- If the current node has a right child, the successor is the leftmost node in that right subtree.
- Otherwise, traverse upwards until reaching an ancestor where the current node is in the ancestor's left subtree. That ancestor is the successor.
IteratorSelf& operator++() {
if (nodePtr->rightChild) {
TreeNode* temp = nodePtr->rightChild;
while (temp->leftChild) {
temp = temp->leftChild;
}
nodePtr = temp;
} else {
TreeNode* curr = nodePtr;
TreeNode* parent = curr->parentNode;
while (parent && curr == parent->rightChild) {
curr = parent;
parent = parent->parentNode;
}
nodePtr = parent;
}
return *this;
}
};
RBTree Public Interface
template<typename KeyType, typename DataType, typename KeyExtractor>
class RedBlackTree {
typedef RBTreeNode<DataType> TreeNode;
public:
typedef TreeIterator<DataType> iterator;
iterator begin() {
TreeNode* temp = rootNode;
while (temp && temp->leftChild) {
temp = temp->leftChild;
}
return iterator(temp);
}
iterator end() {
return iterator(nullptr);
}
std::pair<TreeNode*, bool> Insert(const DataType& data) {
// ... Insertion and balancing logic ...
return std::make_pair(newNode, insertionSuccess);
}
private:
TreeNode* rootNode = nullptr;
};
Map Implementation
namespace ContainerLib {
template<typename Key, typename Value>
class Map {
struct MapKeyExtractor {
const Key& operator()(const std::pair<Key, Value>& element) const {
return element.first;
}
};
RedBlackTree<Key, std::pair<const Key, Value>, MapKeyExtractor> rbTree;
public:
typedef typename decltype(rbTree)::iterator iterator;
iterator begin() { return rbTree.begin(); }
iterator end() { return rbTree.end(); }
std::pair<iterator, bool> insert(const std::pair<Key, Value>& element) {
return rbTree.Insert(element);
}
Value& operator[](const Key& k) {
auto result = insert({k, Value{}});
return (result.first)->second;
}
};
}
Set Implementation
namespace ContainerLib {
template<typename Key>
class Set {
struct SetKeyExtractor {
const Key& operator()(const Key& element) const {
return element;
}
};
RedBlackTree<Key, Key, SetKeyExtractor> rbTree;
public:
// Both iterator types are const_iterators to prevent modification.
typedef typename decltype(rbTree)::const_iterator iterator;
typedef typename decltype(rbTree)::const_iterator const_iterator;
iterator begin() const { return rbTree.begin(); }
iterator end() const { return rbTree.end(); }
std::pair<iterator, bool> insert(const Key& element) {
return rbTree.Insert(element);
}
};
}
In the Set implementation, both iterator and const_iterator are aliased to the RBTree's const_iterator. This prevents modification of set elements. The RBTree's Insert method returns a pair<TreeNode*, bool>, which the set::insert can implicitly convert to its required pair<iterator, bool> return type without a type mismatch.