STL源码剖析(二)
容器rb_tree
Red-Black tree(红黑树)是平衡二叉搜索树(balanced binary search tree)中常被使用的一种。平衡二叉搜索树的特点:排列规则有礼 search 和 insert,并保持高度平衡—————无任何节点过深。
rb_tree提供“便利”操作及iterators。按正常规则(++ite)遍历,便能获得排序状态(sorted)。
因为rb_tree有一定的排序规则,所以我们不应该使用rb_tree的iterator改变元素值 编程层面并未禁止此事。 这样设计的原因是:rb_tree是set和map的底层实现依托,而map运行元素的 data 被改变,只有元素的 key 才是不可改变的。
rb_tree提供两种insertion操作:insert_unuque()和insert_equal()。前者表示节点的key一定可以在整个tree中独一无二,否则插入失败;后者表示节点的key可以重复。
template <class Key,
class Value,
class KeyOfValue,
class Compare,
class Alloc = alloc>
class te_tree{
protected:
typedef __rb_tree_node<Value> rb_tree_node;
...
public:
typedef rb_tree_node* link_type;
...
protected:
size_type node_count; //记录rb_tree的节点数量
link_type header; //头指针
Compare key_compare; //key的大小比较规则;应该是个function object
...
}
模板中Key+value一起合成为value,KeyOfValue就是告诉模板如何确定value中的key是什么。
set/multiset
set/multiset是以rb_tree为底层结构,因此有 元素自动排序 的特性。排序是依据 key 进行的,而set/multiset元素的 value和key合一:value就是key。 同时,set/multiset提供遍历操作和迭代器。按照正常的++ite遍历,便能获得排序状态后的序列。
我们无法使用set/multiset的迭代器改变元素的值,会打乱rb_tree中的顺序。所以set/multiset使用的迭代器就是rb_tree的 const iterator ,目的就是禁止用户对元素进行赋值。
set元素的key必须独一无二,因此insert()用的是rb_tree的 insert_unique() ;multiset元素的key可以重复,因此insert()用的是rb_tree的 insert_equal() 。
set
template <class Key,
class Compare = less<Key>,
class Alloc = alloc>
class set {
typedef Key key_type;
typedef Key value_type;
typedef Compare key_compare;
typedef Compare value_compare;
private:
typedef rb_tree<key_type,
value_type,
identity<value_type>,//VC6中没有identity,提供了一个与identity相似的操作
key_compare,
Alloc> rep_type;
rep_type t;
public:
typedef typename rep_type::const_iterator iterator;
...
};
set的所有操作,都转交给底层的t进行操作,所以可以将set看作一个容器适配器(container adapter)。
map/multimap
map/multimap以rb_tree为底层结构,因此有 元素自动排序 的特性。同时,set/multiset提供遍历操作和迭代器。按照正常的++ite遍历,便能获得排序状态后的序列。
我们无法使用map/multimap的迭代器改变元素的key(因为key有其谨慎的排序规则),但是可以用它来改变元素的data。因map/multimap内部将用户指定的 key type 设置为const, 以便能禁止用户对元素的key赋值。
map元素的key必须独一无二,因此insert()用的是rb_tree的 insert_unique() ;multimap元素的key可以重复,因此insert()用的是rb_tree的 insert_equal() 。
map
template <class Key,
class T,
class Compare = less<Key>,
class Alloc=alloc>
class map {
public:
typedef Key key_type;
typedef T data_type;
typedef T mapped_type;
typedef pair<const Key, T> value_type;
typedef Compare key_compare;
private
typedef rb_tree<key_type,
value_type,
select1st<value_type>, //vc6中同样没有此函数,但提供了相似的功能
key_compare,
Alloc> rep_type;
rep_type t;
public:
typedef typename rep_type::iterator iterator;
...
};
map独有的operator[]
map的[]操作符号用来返回指定key所对应的data, 如果key不存在,则会创建一个带有此key的元素放入map。
容器hashtable
hashtable作为unordered_set和unordered_map的底层结构。hashtable主要的思想就是将给定的元素通过哈希函数计算后得到一个哈希码(Hash Code),这个给定的元素就可以看作为value, 而生成的哈希码可以看为key。有以下的结论:
- 如果两个元素相等,那么通过相同的函数计算得到的哈希码一定相同
- 如果两个元素的哈希码相同,并不能说明两个元素是相同的,只能说明两个元素在三列存储结构中,存放于同一个位置。
以int为元素类型,一个hashtable就是用来表现hashcode和元素之间的映射关系,可以定义一个一定大小的数组用来保存这种映射关系。hashtable中的每一个位置称为一个bucket。假设hashtable的大小为53,现在有些需要存放的数据{59, 63, 108, 2, 53, 55}。我们可以用一个简单的哈希函数来计算哈希码:取余法
static int indexFor(int h, int length) {
return h % length;
}
这样59就存放在序号为6的位置;63就存放在序号为10的位置;108就在序号为2的位置上;2就在序号为2的位置上;53就存放在序号为0的位置;55就存放在序号为2的位置。
通过这样的方式可以快速访问到相应的元素,但也会发现108,2,55三个数的哈希码冲突了,产生哈希码的过程中难免会产生冲突,一种好的冲突解决方案决定了hashtable的效率。目前常用的一种冲突解决方案是Separate Chaining。
这样hashtable中每一个bucket都存放一个链表,所有冲突的哈希码都放在这个链表中。虽说链表的查找是一个线型过程,但是当链表不长时查找的效率还是不差的,当链表过长时,可以将链表转换为红黑树提高查找效率。当整个hashtable中存放的数据过多,会因为链表过长导致查找效率急速下降。但是目前这个界限没有明确的界定,根据大家的经验来说,当hashtable中的数据个数大于bucket的个数时,效率就会受到影响。这个时候就需要对hashtable的bucket进行扩充,这个扩充过程一般来说是将bucket的数量乘以2,然后选出这个2倍数附近的一个 素数 。对于这样固定模式的扩充,可以不用在使用时进行计算,而是在程序中将需要扩充的数字写好,在使用时直接调用。
static const unsigned long __stl_prime_list[__stl_num_primes] = {
53, 97, 193, 389, 769,
1543, 3079, 6151, 12289, 24593,
49157, 98317, 196613, 393241, 786433,
1572869, 3145739, 6291469, 12582917, 25165843,
50331653, 100663319, 201326611, 402653189, 805306457,
1610612741, 3221225473ul, 4294967291ul
};
在扩充之后,因为bucket的个数发生了变化所以需要对整个hashtable进行重新计算。
template <class Value,
class Key,
class HashFcn,
class ExtractKey,
class EqualKey,
class Alloc=alloc>
class hashtable {
public:
typedef HashFcn hasher;
typedef EqualKey key_equal;
typedef size_t size_type;
private:
hasher hash;
key_equal equals;
ExtractKey get_key;
typedef __hashtable_node<Value> node;//单向链表
vector<node*, Alloc> buckets;
size_type num_elements;
public:
size_type bucket_count() const { return buckets.size();}
...
}