所谓关联容器:
每笔数据(每个元素)都有一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部结构(可能是RB-Tree 也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素置放于适当的位置。
关联容器没有所谓的头尾(只有最大元素和最小元素),所以不会有所谓的push_back(), push_front(), pop_back(), pop_front(), begin(), end()这样的行为
一般而言,关联式容器的内部结构是一个平衡二叉树,以便获得良好的搜寻效率。二叉平衡树有许多种类型,包括 AVL-tree, RB-tree, AA-tree, 最被广泛用在STL的是RB-tree
路径长度(length):
根节点至任何节点之间有唯一路径,路径所经过的边数
深度(depth):
根节点至任一节点的路径长度
根节点的深度永远为0
高度(height):
某节点至其最深子节点(叶子节点)的路径长度
节点大小(size):
其所有子代的节点总数
RB-tree结构设计
为了有更大的弹性,节点分为了两层。
//第一层
//可见为了方便,将红黑两色设定为true和false
typedef bool__rb_tree_color_type;
const__rb_tree_color_type __rb_tree_red = false;
const__rb_tree_color_type __rb_tree_black = true;
//底层的红黑树节点(即不包含数据的树)
//一层结构体还是很容易理解的
//作为一棵二叉搜索树,最小值和最大值是很容易取得的
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;
//返回最小元素
static base_ptr minimum(base_ptr x)
{
while (x->left != 0) x = x->left;
return x;
}
//返回最大元素
static base_ptr maximum(base_ptr x)
{
while (x->right != 0) x = x->right;
return x;
}
};
//第二层
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
typedef __rb_tree_node<Value>* link_type;
Value value_field;
};
RB-tree的迭代器
//第一层迭代器
struct__rb_tree_base_iterator
{
//在第一层RB-tree结构体中typedef__rb_tree_node_base* base_ptr;
typedef __rb_tree_node_base::base_ptr base_ptr;
typedef bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
base_ptr node;
//这个即使找下一个节点的意思,用于operate ++
void increment()
{
if (node->right != 0) { //右子存在,即找右子中在最左
node = node->right;
while (node->left != 0)
node = node->left;
}
else { //右子不存在,即找某个父节点
base_ptr y = node->parent;
while (node == y->right) {
node = y;
y = y->parent;
}
//以上的循环我们可以理解,但是y不是一直是node节点的parent吗?
//普通情况下,node->right是肯定不会等于y的,所以最终node也会成功指向要寻找的节点
//但是,我们这个RB-tree是有个header的
//以下我们会画图理解
if (node->right != y)
node = y;
}
}
//用于operate--
void decrement()
{
if (node->color == __rb_tree_red && //这种情况可能较难理解,等下看图
node->parent->parent == node)
node = node->right;
else if (node->left != 0) { //左子存在,即找左子的最右
base_ptr y = node->left;
while (y->right != 0)
y = y->right;
node = y;
}
else {
base_ptr y = node->parent; //否则找某个父节点
while (node == y->left) {
node = y;
y = y->parent;
}
node = y; //成功找到目标节点
}
}
};
要理解上面两个函数的两个难点,我们就要看下这个RB-tree的特殊之处导致的特殊情况:
可见,RB-tree是有一个不装数据的header的,左子指向最小,右子指向最大,parent指向root。
且header颜色是红的
难点一:
当我们调用increment函数时候,当前node即为root节点,此时想要寻找root的下一个节点。发现root节点并没有右子,只能向上寻找。想要找到以子节点是父节点左子节点的父节点作为返回值。因为此时特殊情况header的右子指向root,因此header作为当前节点继续往上寻找。因为header的parent为root,此时root的右子并不等于header,循环结束。此时出现的特殊情况是header也不是root的左子,反倒是node->right != y,即header->right=root,这种情况下node也不再需要等于y,因为我们的目标返回节点就是header
难点二:
当我们调用decrement函数时候,当前node为header(或者说是end()),该节点的parent的parent即是本身,我们要求返回的就是根节点。否则按照正常的逻辑来的话,会返回节点7。假设返回的是7,那么节点7调用decrement函数返回到header,header返回的又是节点7,就不合常理了
RB-tree的真正迭代器:
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 { returnlink_type(node)->value_field; }
#ifndef__SGI_STL_NO_ARROW_OPERATOR
pointer operator->() const { return &(operator*()); }
#endif /*__SGI_STL_NO_ARROW_OPERATOR */
self& operator++() { increment();return *this; }
self operator++(int) {
self tmp = *this;
increment();
return tmp;
}
self& operator--() { decrement();return *this; }
self operator--(int) {
self tmp = *this;
decrement();
return tmp;
}
};
以上即为这里关于RB-tree的内容,细致实现之前已经有过,这里就不再重复。
关于Set
Set的特性是所有元素都会根据元素的键值自动被排序。Set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。Set不允许两个元素具有相同的键值(就是说用的是RB-tree中的insert_unique插入方法)
我们不能通过set的迭代器更改set的元素值,因为set的元素值就是其键值,关系到其排列规则。(通过查看set实现中关于iterator的声明我们可以看出来:
typedef typename rep_type::const_iterator iterator;
)
Set以RB-tree为底层机制,几乎所有的set操作行为,都只是转调用RB-tree的操作
因此,如果希望深入的了解set,那么理解RB-tree是最直观的方法
我们可以调用iterator的基础find函数来寻找我们需要的元素,但是通过内部实现我们可以知道它是通过遍历所有元素来查找的,复杂度为O(n)。所以,如果在用RB-tree为底层实现的数据结构中进行寻找时,我们一般使用该结构自己实现的find函数(即set内部实现的find函数,复杂度为O(logn))
关于Map
Map的特性是,所有元素都会根据元素的键值自动被排序。Map的所有元素都是pair,同时
拥有实值和键值。Map不允许两个元素拥有相同的键值
我们可以通过迭代器修改元素的实值,但不允许修改其键值
Map的底层实现也是RB-tree