关联式容器分为set
(集合)和map
(映射)两大类,以及这两大类衍生体multiset
(多键集合)和multimap
(多键映射)。这些容器的底层机制均以RB-tree(红黑树)
完成。RB-tree
是一个独立的容器,并不开放给外界使用。
此外SGI STL还提供了一个不再标准规格之列的关联式容器:hash table(散列表)
,以及以此hash table
为底层机制而完成的hash_set(散列集合)
,hash_map(散列映射表)
,hash_multiset(散列多键集合)
,hash_multimap(散列多键映射表)
。hash table(散列表)及其衍生容器相当重要,它们未被纳入C++标准的原因是,提案太迟了。
一般而言,关联式容器(map, multimap)内部结构是一个 balanced binary tree(平衡二叉树),以便获得良好的搜寻效率,其中包括:AVL-tree(AVL树),RB-tree(红黑树),AA-tree,其中最被广泛运用于STL的是RB-tree。
本文简要的分析rb_tree在SGI STL中的实现,主要包括:
rb-tree节点设计
rb-tree迭代器设计
rb_tree 的构造以及内存管理
rb_tree 元素的操作
1. rb-tree节点设计
为了有更大的弹性,节点设计分为两层:
//节点颜色类型,非红即黑
typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false;
const __rb_tree_color_type __rb_tree_black = true;
下面的base仅仅有指针,没有实值()
struct __rb_tree_node_base{
typedef __rb_tree_color_type color_type; //节点颜色类型
typedef __rb_tree_node_base* base_ptr; //基本节点指针
color_type color; //节点颜色
base_ptr parent; //指向父节点
base_ptr left; //指向左孩子节点
base_ptr right; //指向右孩子节点
//RB-Tree最小节点,即最左节点
static base_ptr minimum(base_ptr x)
{
while (x->left != 0) x = x->left;
return x;
}
//RB-Tree最大节点,即最右节点
static base_ptr maximum(base_ptr x)
{
//一直往RB-Tree的右边走,最右的节点即是最大节点
//这是二叉查找树的性质
while (x->right != 0) x = x->right;
return x;
}
};
template<typename Value>
struct __rb_tree_node:public __rb_tree_node_base{
typedef __rb_tree_node<Value>* link_type; // 节点指针,指向数据节点
Value value_field; // 节点数据域,即关键字
};
下面是RB-tree的节点图标,其中将__rb_tree_node::value_field填为10:
2. rb-tree迭代器设计
RB-tree实现一个泛型容器,其迭代器设计是关键。主要考虑迭代器的类别,前进,后退,提领,成员访问等操作。它的迭代器设计也是双层结构。使用双向迭代器,但不具有随机定位能力。其前进和后退操作,利用基层迭代器的前进和后退操作实现。下面是对这部分源码的分析。
注意:基类iterator里面并没有value, 只有指针。
struct __rb_tree_base_iterator{
typedef __rb_tree_node_base::base_ptr base_ptr;
typedef ptrdiff_t difference_type;
base_ptr node; //节点指针,连接容器
/* 重载运算符++和--;目的是找到前驱和后继节点. */
//下面只是为了实现oprerator++的,其他地方不会调用.
//RB-Tree的后继节点
void increment();
//RB-Tree的前驱节点
void decrement();
};
上面的increment, decrement找的是二叉树节点的前驱(比当前值小的元素)和后继(比当前值大的元素):如下图,节点25的前驱是19,后继节点是29
下面看increment函数(不用全部理解,只用知道,例如节点如果有右节点,那么右节点的最左的孩子节点就是后继节点)
void __rb_tree_base_iterator::increment()
{
//【情况1】:存在右子树,则找出右子树的最小节点
if (node->right != 0) {//如果有右子树
node = node->right;//向右边走
while (node->left != 0)//往右子树中的左边一直走到底
node = node->left;//最左节点就是后继结点
}
//没有右子树,但是RB-Tree中节点node存在后继结点
else {
base_ptr __y = node->parent;//沿其父节点上溯
while (node == __y->right) { //【情况2】:若节点是其父节点的右孩子,则上溯
node = __y; //一直上溯,直到“某节点不是其父节点的右孩子”为止
__y = __y->parent;
}
if (node->right != __y)//【情况3】:若此时的右子节点不等于此时的父节点
node = __y;//此时的父节点即为解答
}
}
decrement函数同理,故而先省略。
继承类iterator,也只是包含指针,并没有节点value(不过也是迭代器本质就是指针,里面也不应该有对应的value)
//RB-Tree的迭代器iterator结构
//继承基类迭代器Rb_tree_base_iterator
template <class Value, class Ref, class Ptr>
struct _rb_tree_iterator : public __rb_tree_base_iterator {
//迭代器的内嵌类型
typedef Value value_type;
typedef Ref reference;
typedef Ptr pointer;
typedef _rb_tree_iterator<Value, Value &, Value *> iterator;
typedef _rb_tree_iterator<Value, const Value &, const Value *> const_iterator;
typedef _rb_tree_iterator<Value, Ref, Ptr> self;
typedef __rb_tree_node <Value> *link_type;//节点指针
//构造函数
_rb_tree_iterator() {}
_rb_tree_iterator(link_type x) { node = x; }
_rb_tree_iterator(const iterator& it) { node = it.node; }
// 以下是操作符重载
reference operator*() const { return link_type(node)->value_field; }
pointer operator->() const { return &(operator*()); }
};
接下来看下operator++,operator–主要是调用__rb_tree_base_iterator的increment, decrement:
//前缀operator++找出后继节点,调用基类的 increment()
self& operator++() { increment(); return *this; }
//后缀operator++找出后继节点,调用基类的 increment()
self operator++(int) {
self tmp = *this;
increment();
return tmp;
}
//前缀operator--找出前驱节点,调用基类的decrement()
self& operator--() { decrement(); return *this; }
//后缀operator--找出前驱节点,调用基类的decrement()
self operator--(int) {
self tmp = *this;
decrement();
return tmp;
}
3. rb_tree 的构造以及内存管理
re_tree的定义如下(下面做了一定的简化不影响理解,具体的见set,map那一节):
template<typename Value, typename Compare>
class rb_tree{
protected:
typedef typename __rb_tree_node_base* base_ptr;
typedef __rb_tree_node<Value> rb_tree_node;
// 空间配置器,一次分配一个节点
typedef allocator<rb_tree_node> rb_tree_node_allocator;
typedef __rb_tree_color_type color_type;
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef Value value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef rb_tree_node* link_type;
typedef _rb_tree_iterator<Value, Value&, Value*> iterator;
protected:
//一下三个成员,表现rb_tree
size_type node_count; // 记录节点数量
link_type header; // 使用上的一个技巧
Compare key_compare; // 键值大小比较准则
}
注意rb_tree的根节点的设计,见下图的初始化以及最右边的结果:(header和root互为对方的父节点,这是一种实现技巧)
首先看下最简单的初始化,构造函数:
template<typename Value, typename Compare>
rb_tree<Value, Compare>::rb_tree(const Compare& comp = Compare()){
node_count = 0; //节点个数为0
key_compare(comp); //比较函数
init();
}
接下来看init函数:
1. 节点的分配以及构造:
// 分配一个新结点(分配内存), 注意这里并不进行构造,
link_type alloc_node(){
return rb_tree_node_allocator::allocate();
}
// 产生(配置并构造)一个节点, 首先分配内存, 然后进行构造
link_type alloc_ctor_node(const value_type& value){
link_type tmp = alloc_node(); // 配置空间
construct(tmp, value); // 构造节点
return tmp;
}
- 常用节点的返回(返回的是引用,const限定符函数):
link_type& root()const{
return (link_type&)header->parent;
}
link_type& leftmost()const{
return (link_type&)header->left;
}
link_type& rightmost()const{
return (link_type&)header->right;
}
template<typename Value, typename Compare>
void rb_tree<Value, Compare>::init(){
header = alloc_node(); // 申请一个节点(header无需构造)
//以下几个函数返回的是引用
color(header) = __rb_tree_red; // 令header红色,区分root
root() = 0;
leftmost() = header;
rightmost() = header;
}
4. rb_tree 元素的操作
re_tree的核心函数如下(不给出具体的定义):
// 插入新的节点,节点键值允许重复
iterator insert_equal(const Value& v);
// 插入新的节点,节点键值不允许重复,重复则无效
iterator insert_unique(const Value& v);
// 节点查找,比STL算法find高效
iterator find(const Value& v);
private:
// 真正执行,插入新的节点
iterator _insert(base_ptr parent, base_ptr new_p, const Value& v);
// 旋转保持树平衡
void __rb_tree_rebalance(__rb_tree_node_base* x,
__rb_tree_node_base*& root);
// 左旋转
void __rb_tree_rotate_left(__rb_tree_node_base* x,
__rb_tree_node_base*& root);
// 右旋转
void __rb_tree_rotate_right(__rb_tree_node_base* x,
__rb_tree_node_base*& root);