第3章:map/set - 赤黒木
はじめに
mapとsetは、平衡二分探索木(赤黒木)を使用して実装される連想コンテナです。本章では、赤黒木の理論と実装を学びます。
---
1. 赤黒木の理論
1.1 二分探索木の問題
通常の二分探索木は、入力順序によって偏る可能性があります:
理想的な木: 偏った木(最悪ケース):
4 1
/ \ \
2 6 2
/ \ / \ \
1 3 5 7 3
\
4
高さ: O(log n) 高さ: O(n)
1.2 赤黒木の性質
Leo J. Guibas と Robert Sedgewick が1978年に発表した赤黒木は、以下の5つの性質を持ちます:
1. 各ノードは赤か黒
2. ルートは黒
3. 全ての葉(NIL)は黒
4. 赤ノードの子は両方とも黒
5. 各ノードから葉までの黒ノード数は同じ(黒高さ)
B:8
/ \
R:4 B:12
/ \ / \
B:2 B:6 R:10 R:14
B = Black, R = Red
1.3 計算量の保証
赤黒木の高さは最大で 2 * log₂(n+1) に制限されます:
| 操作 | 平均 | 最悪 | |------|------|------| | 検索 | O(log n) | O(log n) | | 挿入 | O(log n) | O(log n) | | 削除 | O(log n) | O(log n) |
---
2. ノードの構造
2.1 ノードの定義
enum RBColor { RED, BLACK };
template<typename T>
struct RBNode {
T data;
RBNode* parent;
RBNode* left;
RBNode* right;
RBColor color;
RBNode(const T& val)
: data(val), parent(NULL), left(NULL), right(NULL), color(RED) {}
};
2.2 NILノード
NILノードは、全ての葉を表すセンチネルです:
template<typename T, typename Compare, typename Alloc>
class RBTree {
private:
typedef RBNode<T> node_type;
typedef node_type* node_pointer;
node_pointer _root;
node_pointer _nil; // センチネル(黒、共有)
size_t _size;
Compare _comp;
Alloc _alloc;
public:
RBTree(const Compare& comp = Compare(),
const Alloc& alloc = Alloc())
: _root(NULL), _size(0), _comp(comp), _alloc(alloc) {
// NILノードを作成
_nil = _alloc.allocate(1);
_nil->color = BLACK;
_nil->parent = NULL;
_nil->left = NULL;
_nil->right = NULL;
_root = _nil;
}
};
---
3. 回転操作
3.1 左回転
x y
/ \ / \
a y => x c
/ \ / \
b c a b
y が x の右の子
→ y が新しい親になり、x が y の左の子になる
template<typename T, typename Compare, typename Alloc>
void RBTree<T, Compare, Alloc>::_left_rotate(node_pointer x) {
node_pointer y = x->right;
// y の左部分木を x の右部分木にする
x->right = y->left;
if (y->left != _nil) {
y->left->parent = x;
}
// y の親を x の親にする
y->parent = x->parent;
if (x->parent == _nil) {
_root = y;
} else if (x == x->parent->left) {
x->parent->left = y;
} else {
x->parent->right = y;
}
// x を y の左の子にする
y->left = x;
x->parent = y;
}
3.2 右回転
y x
/ \ / \
x c => a y
/ \ / \
a b b c
template<typename T, typename Compare, typename Alloc>
void RBTree<T, Compare, Alloc>::_right_rotate(node_pointer y) {
node_pointer x = y->left;
// x の右部分木を y の左部分木にする
y->left = x->right;
if (x->right != _nil) {
x->right->parent = y;
}
// x の親を y の親にする
x->parent = y->parent;
if (y->parent == _nil) {
_root = x;
} else if (y == y->parent->left) {
y->parent->left = x;
} else {
y->parent->right = x;
}
// y を x の右の子にする
x->right = y;
y->parent = x;
}
---
4. 挿入
4.1 挿入アルゴリズム
template<typename T, typename Compare, typename Alloc>
typename RBTree<T, Compare, Alloc>::node_pointer
RBTree<T, Compare, Alloc>::insert(const T& val) {
// 1. 通常のBST挿入
node_pointer z = _create_node(val);
node_pointer y = _nil;
node_pointer x = _root;
while (x != _nil) {
y = x;
if (_comp(z->data, x->data)) {
x = x->left;
} else if (_comp(x->data, z->data)) {
x = x->right;
} else {
// 重複キー(mapの場合は挿入しない)
_destroy_node(z);
return x;
}
}
z->parent = y;
if (y == _nil) {
_root = z;
} else if (_comp(z->data, y->data)) {
y->left = z;
} else {
y->right = z;
}
z->left = _nil;
z->right = _nil;
z->color = RED;
++_size;
// 2. 赤黒木の性質を修復
_insert_fixup(z);
return z;
}
4.2 挿入後の修復
挿入後に赤黒木の性質が崩れる可能性があるケース:
Case 1: 叔父が赤
g(B) g(R)
/ \ / \
p(R) u(R) => p(B) u(B)
/ /
z(R) z(R)
Case 2: 叔父が黒、zが右の子
g(B) g(B)
/ \ / \
p(R) u(B) => z(R) u(B)
\ /
z(R) p(R)
Case 3: 叔父が黒、zが左の子
g(B) p(B)
/ \ / \
p(R) u(B) => z(R) g(R)
/ \
z(R) u(B)
template<typename T, typename Compare, typename Alloc>
void RBTree<T, Compare, Alloc>::_insert_fixup(node_pointer z) {
while (z->parent->color == RED) {
if (z->parent == z->parent->parent->left) {
node_pointer y = z->parent->parent->right; // 叔父
if (y->color == RED) {
// Case 1: 叔父が赤
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->right) {
// Case 2: 叔父が黒、zが右の子
z = z->parent;
_left_rotate(z);
}
// Case 3: 叔父が黒、zが左の子
z->parent->color = BLACK;
z->parent->parent->color = RED;
_right_rotate(z->parent->parent);
}
} else {
// 対称的なケース(左右を入れ替え)
node_pointer y = z->parent->parent->left;
if (y->color == RED) {
z->parent->color = BLACK;
y->color = BLACK;
z->parent->parent->color = RED;
z = z->parent->parent;
} else {
if (z == z->parent->left) {
z = z->parent;
_right_rotate(z);
}
z->parent->color = BLACK;
z->parent->parent->color = RED;
_left_rotate(z->parent->parent);
}
}
}
_root->color = BLACK;
}
---
5. 削除
5.1 削除アルゴリズム
削除は挿入より複雑です:
template<typename T, typename Compare, typename Alloc>
void RBTree<T, Compare, Alloc>::erase(node_pointer z) {
node_pointer y = z;
node_pointer x;
RBColor y_original_color = y->color;
if (z->left == _nil) {
// Case 1: 左の子がない
x = z->right;
_transplant(z, z->right);
} else if (z->right == _nil) {
// Case 2: 右の子がない
x = z->left;
_transplant(z, z->left);
} else {
// Case 3: 両方の子がある
y = _minimum(z->right); // 後続ノードを見つける
y_original_color = y->color;
x = y->right;
if (y->parent == z) {
x->parent = y;
} else {
_transplant(y, y->right);
y->right = z->right;
y->right->parent = y;
}
_transplant(z, y);
y->left = z->left;
y->left->parent = y;
y->color = z->color;
}
_destroy_node(z);
--_size;
if (y_original_color == BLACK) {
_erase_fixup(x);
}
}
template<typename T, typename Compare, typename Alloc>
void RBTree<T, Compare, Alloc>::_transplant(node_pointer u, node_pointer v) {
if (u->parent == _nil) {
_root = v;
} else if (u == u->parent->left) {
u->parent->left = v;
} else {
u->parent->right = v;
}
v->parent = u->parent;
}
5.2 削除後の修復
template<typename T, typename Compare, typename Alloc>
void RBTree<T, Compare, Alloc>::_erase_fixup(node_pointer x) {
while (x != _root && x->color == BLACK) {
if (x == x->parent->left) {
node_pointer w = x->parent->right; // 兄弟
if (w->color == RED) {
// Case 1: 兄弟が赤
w->color = BLACK;
x->parent->color = RED;
_left_rotate(x->parent);
w = x->parent->right;
}
if (w->left->color == BLACK && w->right->color == BLACK) {
// Case 2: 兄弟の両方の子が黒
w->color = RED;
x = x->parent;
} else {
if (w->right->color == BLACK) {
// Case 3: 兄弟の右の子が黒
w->left->color = BLACK;
w->color = RED;
_right_rotate(w);
w = x->parent->right;
}
// Case 4: 兄弟の右の子が赤
w->color = x->parent->color;
x->parent->color = BLACK;
w->right->color = BLACK;
_left_rotate(x->parent);
x = _root;
}
} else {
// 対称的なケース
// ...
}
}
x->color = BLACK;
}
---
6. mapの実装
6.1 クラス定義
namespace ft {
template<typename Key, typename T,
typename Compare = std::less<Key>,
typename Alloc = std::allocator<std::pair<const Key, T> > >
class map {
public:
typedef Key key_type;
typedef T mapped_type;
typedef std::pair<const Key, T> value_type;
typedef Compare key_compare;
typedef Alloc allocator_type;
typedef typename allocator_type::reference reference;
typedef typename allocator_type::const_reference const_reference;
typedef typename allocator_type::pointer pointer;
typedef typename allocator_type::const_pointer const_pointer;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
// value_compare(ペアのfirstを比較)
class value_compare {
friend class map;
protected:
Compare comp;
value_compare(Compare c) : comp(c) {}
public:
bool operator()(const value_type& x, const value_type& y) const {
return comp(x.first, y.first);
}
};
private:
typedef RBTree<value_type, value_compare,
typename Alloc::template rebind<RBNode<value_type> >::other>
tree_type;
tree_type _tree;
key_compare _comp;
public:
typedef typename tree_type::iterator iterator;
typedef typename tree_type::const_iterator const_iterator;
typedef ft::reverse_iterator<iterator> reverse_iterator;
typedef ft::reverse_iterator<const_iterator> const_reverse_iterator;
// コンストラクタ
explicit map(const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
template<typename InputIterator>
map(InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());
map(const map& x);
// ...
};
} // namespace ft
6.2 主要メンバ関数
// operator[]
template<typename Key, typename T, typename Compare, typename Alloc>
typename map<Key, T, Compare, Alloc>::mapped_type&
map<Key, T, Compare, Alloc>::operator[](const key_type& k) {
iterator it = find(k);
if (it == end()) {
it = insert(ft::make_pair(k, mapped_type())).first;
}
return it->second;
}
// insert
template<typename Key, typename T, typename Compare, typename Alloc>
std::pair<typename map<Key, T, Compare, Alloc>::iterator, bool>
map<Key, T, Compare, Alloc>::insert(const value_type& val) {
iterator it = find(val.first);
if (it != end()) {
return std::make_pair(it, false);
}
return std::make_pair(_tree.insert(val), true);
}
// find
template<typename Key, typename T, typename Compare, typename Alloc>
typename map<Key, T, Compare, Alloc>::iterator
map<Key, T, Compare, Alloc>::find(const key_type& k) {
return _tree.find(ft::make_pair(k, mapped_type()));
}
---
7. イテレータの実装
7.1 木のイテレータ
template<typename T>
class RBTreeIterator {
public:
typedef T value_type;
typedef T& reference;
typedef T* pointer;
typedef std::bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
private:
typedef RBNode<T>* node_pointer;
node_pointer _node;
node_pointer _nil;
public:
RBTreeIterator() : _node(NULL), _nil(NULL) {}
RBTreeIterator(node_pointer node, node_pointer nil)
: _node(node), _nil(nil) {}
reference operator*() const { return _node->data; }
pointer operator->() const { return &(_node->data); }
// インクリメント(次のノードに移動)
RBTreeIterator& operator++() {
if (_node->right != _nil) {
// 右部分木がある場合、右部分木の最小値
_node = _node->right;
while (_node->left != _nil) {
_node = _node->left;
}
} else {
// 右部分木がない場合、親を辿る
node_pointer p = _node->parent;
while (p != _nil && _node == p->right) {
_node = p;
p = p->parent;
}
_node = p;
}
return *this;
}
RBTreeIterator operator++(int) {
RBTreeIterator tmp = *this;
++(*this);
return tmp;
}
// デクリメント(前のノードに移動)
RBTreeIterator& operator--() {
if (_node->left != _nil) {
_node = _node->left;
while (_node->right != _nil) {
_node = _node->right;
}
} else {
node_pointer p = _node->parent;
while (p != _nil && _node == p->left) {
_node = p;
p = p->parent;
}
_node = p;
}
return *this;
}
bool operator==(const RBTreeIterator& other) const {
return _node == other._node;
}
bool operator!=(const RBTreeIterator& other) const {
return _node != other._node;
}
};
---
まとめ
本章で学んだこと:
- 赤黒木の性質: 5つの不変条件
- 回転操作: 左回転、右回転
- 挿入: BST挿入と赤黒木の修復
- 削除: 複雑なケース分け
- mapの構造: 赤黒木を使用した実装
- イテレータ: 木を順序付きで走査
次章では、stack(コンテナアダプタ)を学びます。