《STL源码剖析》学习笔记-第5章 关联式容器(二)

1、set和multiset

set的特性:

(1)所有元素都会根据元素的键值自动被排序。

(2)set是集合,它的元素的键值就是实值,实值就是键值,不允许两个元素有相同的值。

(3)不可以通过set的iterator来改变元素的值,因为set的元素值就是键值,改变键值会违反元素排列的规则。

(4)在客户端对set进行插入或删除操作后,之前的迭代器依然有效。当然,被删除的元素的迭代器是个例外。

(5)它的底层机制是RB-tree。几乎所有的操作都只是转调用RB-tree的操作行为而已。

multiset和set几乎一样,唯一的区别是,multiset允许键值重复。因此set使用底层RB-tree的insert_unique()实现插入,而multiset插入采用的是RB-tree的insert_equal()而非insert_unique()

测试实例:

#include <set>
#include <iostream>

using namespace std;
int main()
{
    int ia[5] = {0,1,2,3,4};
    int n = sizeof(ia)/sizeof(ia[0]);
    cout<<n<<endl;// 5
    set<int> iset(ia, ia+n);
    cout<<"size="<<iset.size() << endl;// size=5
    cout<<iset.count(3)<<endl;// 1
    iset.insert(3);//插入无效,元素不允许重复
    cout<<"size="<<iset.size() << endl;// size=5
    cout<<iset.count(3)<<endl;// 2

    iset.erase(1);
    cout<<iset.count(1)<<endl;// 0

    set<int>::iterator ite1=iset.begin();
    set<int>::iterator ite2=iset.end();
    for(; ite1 != ite2; ++ite1)
        cout<<*ite1<<" ";
    cout<<endl;//0 2 3 4

    //关联式容器,应采用其所提供的find函数搜寻元素,会比使用
    //STL算法find()更优效率!因STL find()只是循序搜寻。
    ite1 = iset.find(3);//使用自身提供的find函数
    if(ite1 != iset.end())
        cout<<"3 found!"<<endl;
    else
        cout<<"3 not found!"<<endl;
    //企图通过迭代器改变set元素,是不被允许的
    //*ite1 = 9;//error !
    return 0;
}

2、map和multimap

map的特性:

(1)所有元素都会根据元素的键值自动被排序。

(2)map的所有元素都是pair,第一个值是键值,第二个是实值。

(3)map不允许两个元素拥有相同的键值。

(4)可以通过map的迭代器来改变元素的实值,但不可以改变键值,那样会违反元素的排列规则。

(5)在客户端对map进行插入或删除操作后,之前的迭代器依然有效。当然,被删除的元素的迭代器是个例外。

(6)它的底层机制是RB-tree。几乎所有的操作都只是转调用RB-tree的操作行为而已。

multimap和map几乎一样,唯一的区别是,multimap允许键值重复。因此map使用底层RB-tree的insert_unique()实现插入,而multimap插入采用的是RB-tree的insert_equal()而非insert_unique()

3、hashtable

1、hashtable概述

hashtable可以提供对任意有名项的存取和删除操作,这种结构的用意在于提供常数时间的的基本操作,而不依赖于插入元素的随机性,是以统计为基础的。

散列函数(hash function):负责将某一元素映射为一个”大小可接受之索引”。简而言之,就是将大数映射为小数。

使用hash function带来的问题:可能有不同元素映射到相同的位置(相同索引)。这便是碰撞或冲突问题。解决碰撞问题的方法:线性探测(linear probing),二次探测(quadratic probing),开链(separate chaining)等。stl hashtable采用的hash方式是开链法。

(1)线性探测:当hash function计算出某个元素的插入位置,而该位置空间不再可用时,怎么做?最简单的办法就是循序往下一一寻找,知道找到一个可用空间为止。

需要两个假设:a.表格足够大。b.每个元素都能够独立。

线性探测会造成主集团(primary clustering)问题:平均插入成本的成长幅度,远高于负载系数的成长幅度。

(2)二次探测:主要用来解决主集团问题。解决碰撞的方程式为F(i) = i^2。如果hash function计算出新元素的位置为H,而该位置实际上已被使用,那么就依次尝试H+1^2,H+2^2,H+3^2,H+4^2,....,H+i^2,而不像线性探测尝试的是H+1,H+2,H+3,H+4,....,H+i

二次探测可以消除主集团,却可能造成次集团(secondary clustering):两个元素经hash function计算出来的位置若相同,则插入时所探测的位置也相同,形成某种浪费。消除次集团的方法如复式散列。

(3)开链:这种做法是在每一个表格元素中维护一个list。hash function为我们分配某一个list,然后我们在哪个list身上执行元素的插入、搜寻、删除等操作。若list够短,速度还是够快。

使用开链法,表格的负载系数将大于1。SGI STL hashtable便采用的是开链法。

2、hashtable结构

hashtable表格内的每个单元,涵盖的不只是个节点(元素),而可能是一桶节点,因此称为bucket。

SGI STL中hash table使用的是开链法进行的冲突处理,其结构如图所示:

bucket所维护的linked list不采用STL的list或者slist,而是自行维护hash table node。而至于buckets聚合体,则使用vector来完成,以便有动态扩容能力。

STL中的hash迭代器,是一种forward迭代器,只能+。有指向当前节点的指针和指向对应的vector的指针,没有后退操作,也就是没有所谓的逆向迭代器。

hashtable以质数来设计表格大小,预先计算好了28个质数,以备随时访问,大约都是两倍的关系递增,同时提供一个函数,查询28个质数中,“最接近某数且大于某数”的质数作为vector的长度,如果需要重新分配,则分配下一个质数长度的vector。

stl hash table扩张表格的触发条件是:当元素的数目大于或等于表格的大小。(这个条件应该是为了保证常数操作时间,在统计基础上得出的)。

insert分为insert_uniqueinsert_equal操作,前者保证插入的数不能有重复,后者可以插入键值相同的数。可以先用unique之后再用equal。insert_unique:先调用resize函数,看是否需要增大vector,然后插入,vector的索引通过取余得到。resize:如果已有元素的个数大于vector的size,需要根据得到的最新质数,分配新的空间,将在旧空间的元素,重新计算hash,复制到新的空间,最后旧空间与新空间swap一下即可。insert_equal:也是先调用resize,遍历找到和他相同的节点,在该节点的前面插入。

hashtable有一些无法处理的型别,比如string,double,float。除非用户为那些型别写了相应的hash function。

4、hash_set,hash_map,hash_multiset,hash_multimap

这些容器和前面介绍的一一对应,只不过这些都是以hash_tabel为底层实现机制的。

底层机制决定了这两组容器的区别:

RB-tree组对元素实现排序,而hashtable组没有;

RB-tree组的查找时间复杂度为lg(n),而hashtable组为常数时间;

RB-tree组在空间利用上,不会浪费结点,而hashtable组可能会有一些空置桶。

hash_multisethash_multimap插入使用的是inset_equal。其它操作与multiset和multimap相同。

时间: 2024-11-08 21:07:45

《STL源码剖析》学习笔记-第5章 关联式容器(二)的相关文章

stl源码剖析学习笔记(二)traits编程技法简明例程

解释说明 traits侯捷老师的翻译是萃取.其目的就是在编译期进行模板调用的类型识别,从而做一些事情. 最突出的例子,我觉得不是<STL源码剖析>中"迭代器概念与traits编程技法"这一章的说明,而是stl算法中copy的实现.代码在stl源码的stl_algobase.h中. copy的最终实现,大致分为两类,一类是直接整块内存的memmove操作,另一类是一个个对象赋值.其中涉及has_trivial_assignment_operator的类型推断. 如果has_t

c++ stl源码剖析学习笔记(一)

template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy(InputIterator first, InputIterator last,ForwardIterator result) 函数使用示例 #include <algorithm> #include <iostream> #include <memory> #inclu

STL源码剖析 学习笔记

目录: 第二章 空间适配器 第三章 迭代器 第四章 序列式容器(vector,list,deque,stack,heap,priority_queue,slist) 第五章 关联式容器(树的算法 + RB_tree ,set,map,hashtable) 第六章 算法 第七章 仿函数 第八章 适配器(adapet) 第二章 空间适配器 具有次配置力的SGI空间适配器,STL allocator将对象的内存分配和初始化分离开,内存配置由alloc:allocate 负责,内存释放由dealloca

重温《STL源码剖析》笔记 第三章

第三章:迭代器概念与traits编程技法 迭代器是一种smart pointer auto_Ptr 是一个用来包装原生指针(native pointer)的对象,声明狼藉的内存泄漏问题可藉此获得解决. auto_ptr用法如下,和原生指针一模一样: void func() { auto_ptr<string> ps(new string("jjhou")); cout << *ps << endl; //输出:jjhou cout <<

重温《STL源码剖析》笔记 第四章

源码之前,了无秘密  ——侯杰 第四章:序列式容器 C++语言本身提供了一个序列式容器array array:分配静态空间,一旦配置了就不能改变. vector: 分配动态空间.维护一个连续线性空间,迭代器类型为:Random Access Iterators 空间配置 typedef simple_alloc<value_type, Alloc> data_allocator 所谓动态增加空间大小,并不是在原空间之后接续新空间,而是以原大小的两倍另外配置一块较大 的空间,然后将原内容拷贝过来

c++ stl源码剖析学习笔记(二)iterator auto_ptr(老版本书籍示例 新版本C++中已经废除此概念)

ITERATOR template<class InputIterator,class T> InputIterator find(InputIterator first,InputIterator last,const T& value) { while(first != last && *first != value) ++first; return first; } 代码示例 1 #include <iostream> 2 #include <v

重温《STL源码剖析》笔记 第一章

源码之前,了无秘密. --侯杰 经典的书,确实每看一遍都能重新收获一遍: 第一章:STL简介 STL的设计思维:对象的耦合性极低,复用性极高,符合开发封闭原则的程序库. STL的价值:1.带给我们一套极具实用价值的零部件,以及一个整合的组织. 2.带给我们一个高层次的以泛型思维为基础的.系统化的.条理分明的“软件组件分类学”. 在STL接口之下,任何组件都有最大的独立性,并以所谓迭代器胶合起来,或以配接器互相配接,或以所 谓仿函数动态选择某种策略. STL六大组件:1.容器(containers

STL 源码剖析读书笔记一:空间配置器

1. STL 的空间配置器 STL 空间配置器在运用的角度来说,是最不需要介绍的,它总是隐藏在一切组件背后.但若以 STL 的实现角度而言,第一个需要理解的就是空间配置器. 根据 STL 规范,以下是 allocator 的必要接口: allocator::value_type allocator::pointer allocator::const_pointer allocator::reference allocator::const_reference allocator::size_ty

STL 源码剖析读书笔记三:序列式容器之 vector、list

1. STL 中的容器 容器,置物之所也.STL 容器即是将运用最广的一些数据结构实现出来.如下图所示: 上图以内缩方式来表达基层和衍生层的关系.所谓衍生,并非派生关系,而是内含关系.例如 heap 内含一个 vector,priority-queue 内含一个 heap.stack.queue都内含一个 deque,set/map/multimap/multiset 都内含一个 RB-tree,hash_x 都内含一个 hashtable. 2. 序列式容器之 vector 所谓序列式容器,其