• 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();}
    ...
    }
    
  • 相关阅读:
    win32-LPCSTR->String
    win32-UpdateLayeredWindow
    win32-CreateDIBSection的使用
    win32-StretchDIBits
    hdu1978 简单记忆化搜索
    hdu1978 简单记忆化搜索
    hdu4530 水题
    hdu4530 水题
    hdu3006 状态压缩+位运算+hash(小想法题)
    hdu3006 状态压缩+位运算+hash(小想法题)
  • 原文地址:https://www.cnblogs.com/joker-wz/p/10239766.html
Copyright © 2020-2023  润新知