《STL源码剖析》读书笔记(三)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hgyan25/article/details/81132461
  • 关联式容器(缩进表示基层与衍生层的关系,衍生是内含关系)

    • RB-tree(非公开)
      • set
      • map
      • multiset
      • multimap
    • hashtable(非标准)
      • hash_set(非标准)
      • hash_map(非标准)
      • hash_multiset(非标准)
      • hash_multimap(非标准)
  • 二叉搜索树
    任何节点最多只能允许两个子节点

  • 平衡二叉树
    确保整棵树的深度为O(logN),左右子树的高度最多差1

  • set

    • 特性:所有元素都会根据元素的键值自动被排序。键值就是实值,实值就是键值。不允许两个元素有相同的键值。
    • 以RB tree为底层机制,几乎所有的set操作行为,都是转调用RB-tree的操作行为。
  • map

    • 所有元素都会根据元素的键值自动被排序,以RB-tree为底层机制,每个节点的内容是一个pair。同时拥有实值和键值。pair的第一个元素被看做是键值,第二个元素是实值。map不允许两个元素拥有相同的键值。
    • 不能通过map的迭代器改变map的元素内容,不能修正元素的键值。因为map的键值关系到map的元素的排列规则,任意改变map元素的键值都会严重破坏map的组织。但是可以修改元素的实值。
    • 进行insert或者erase后,迭代器依然有效。
    • subscript操作符,可能作为左值运算,也可能作为右值运算
      T& operator[](const key_type& k){
          return (*((insert(value_type(k,T()))).first)).second;
      }
  • multiset
    特性与用法与set完全相同,唯一的差别在于它允许键值重复,插入的时候用的是RB-tree的insert_equal()而不是insert_unique()

  • multimap
    特性与用法与map完全相同,唯一的差别在于它允许键值重复,插入的时候用的是RB-tree的insert_equal()而不是insert_unique()

  • hash_table(散列表)
    hash table可以提供所有的有名项的存取操作和删除操作,也可以视作是一种字典结构,意在提供常数时间的基本操作。

  • 所有元素都是16bits且不带正负号的整数,范围为0-65535,那么用一个array来记录,每个元素值代表相应元素出现的次数。如果i出现,那么A[i]++。

    • 问题1:如果元素是32bit,那么array的大小为2^32=4GB。
    • 问题2:如果元素是字符串,无法映射到array的索引中。可以通过字符编码来实现。
    • 避免大的荒谬的array:使用某种映射函数,将大数映射为小数,将一个元素映射为一个“大小可接受之索引”,也叫hash_function。
  • 解决哈希函数出现的冲突问题

    • 线性探测,依序往下一一寻找(如果到达了尾端就从头开始寻找),直至找到一个可用空间为止。
      问题:平均插入成本的成长幅度,远高于负载系数的成长幅度
    • 二次探测,依序尝试H+1, H+4, H+9…H+i^2(可用位移法来计算)
      用于消除主集团,却可能造成次集团(可用double hashing来消除次集团)
    • 开链法:表格内的每个单元,涵盖的不只是节点(元素),也可能是一“桶”节点。bucket维护的是一个linked list。
  • SGI STL的hash table就是采用开链法
    整个hash table是由vector和linked list组成。

    扫描二维码关注公众号,回复: 3393206 查看本文章
    • 迭代器结构
      node * cur;//迭代器目前所指之节点
      hashtable * ht;//保持对容器的连接关系,可能需要从一个bucket跳到另一个bucket
      没有后退操作,也就是没有定义所谓的逆向迭代器
    • hash table的数据结构
      bucket聚合体以vector来完成,便于动态扩充:vector
# include<hash_set>
hashtable<int, int, hash<int>, identity<int>, equal_to<int>, alloc>iht(50, hash<int>, equal_to<int>());
iht.size()=0
iht.bucket_count()=53

用迭代器遍历hashtable


hashtable<int, int, hash<int>, identity<int>, equal_to<int>, alloc>::iterator ite = iht.begin();
for(int i=0;i<iht.size();++i,++ite)
    cout<<*ite<<' ';
cout<<endl;
用迭代器遍历buckets
hashtable<int, int, hash<int>, identity<int>, equal_to<int>, alloc>::iterator ite = iht.begin();
for(int i=0;i<iht.bucket_count();++i)
    int n = iht.elems_in_bucket(i);
    if(n!=0){
        cout<<"bucket["<<i<<"] has "<<n<<" elems."<<endl;
    }
cout<<endl;
  • hash_set

    • 以hash_table为底层实现,基本所有的hash_set的行为都是转调用hashtable的操作行为而已。
    • set元素的键值就是实值,实值就是键值
    • 插入操作用的是hash_table的insert_unique()
    • RB tree为底层的与hash table为底层的都可以用于快速搜索元素,但是hash_set没有自动排序功能
  • hash_map

    • 以hash_table为底层实现,基本所有的hash_map的行为都是转调用hashtable的操作行为而已。
    • 插入操作用的是hash_table的insert_unique()
    • RB tree为底层的与hash table为底层的都可以根据键值快速搜索元素,但是hash_map没有自动排序功能
  • hash_multiset

    • 以hash_table为底层实现,基本所有的hash_multiset的行为都是转调用hashtable的操作行为而已。
    • 插入操作用的是hash_table的insert_equal()
    • RB tree为底层的与hash table为底层的都可以用于快速搜索元素,但是hash_set没有自动排序功能
  • hash_multiset

    • 以hash_table为底层实现,基本所有的hash_multimap的行为都是转调用hashtable的操作行为而已。
    • 插入操作用的是hash_table的insert_equal()
  • HashMap的原理,内部数据结构
    hashmap用的是hash_table,查询时间复杂度为O(1).用vector来保存每个桶,每个节点又是也用linked list来记录。
    hash_map,首先分配一大片内存,形成许多桶。是利用hash函数,对key进行映射到不同区域进行保存。

    • 其插入过程:
      1、得到key;
      2、通过hash函数得到hash值;
      3、得到桶号(一般都为hash值对桶数求模);
      4、存放key和value在桶内;
    • 其取值过程是:
      1、得到key;
      2、通过hash函数得到hash值;
      3、得到桶号;
      4、比较桶的内部元素是否与key相等,若不相等,则没有找到;
      5、取出相等的记录的value;
  • 什么时候需要使用hash_map,什么时候需要map?
    总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小, hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望 程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且 hash_map的构造速度较慢。
    现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用

  • HashMap 怎样解决冲突,讲一下扩容过程
    hash table 用链式法来解决冲突。
    拿元素个数与bucket_vector的大小来比较,如果前者大于后者,就进行重整。
    要扩容的时候寻找下一个质数。Next_size()

    1. 设立新的bucket_vector
    2. 依序处理每个bucket所含的(串行)的每个节点
      a. 找到这个节点位于哪个新的bucket buck_num(val, n)
      b. 将bucket的指针指向下一个节点
      c. 将当前节点插入到新的bucket中,成为其对应串行的第一个节点
      d. 回到旧bucket所指的待处理的串行中,处理下一个节点
    3. 对调两个buckets. void swap (vector& x); 如果两个vector的大小不同,那么大的变小,小的变大

猜你喜欢

转载自blog.csdn.net/hgyan25/article/details/81132461