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();}
...
}
原文地址:https://www.cnblogs.com/joker-wz/p/10239766.html