一、关联式容器

标准的STL关联式容器分为set(集合)/map(映射表)两大类,以及这两大类的衍生体multiset(多键集合)和 multimap(多键映射表)。这些容器的底层机制均以RB-tree(红黑树)完成。RB-tree也是一个独立容器,但并不开放给外界使用。

此外,SGI STL 还提供了一个不在标准规格之列的关联式容器:hash table (散列表),以及以此hash table 为底层机制而完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多键集合)、hash_multimap(散列多键映射表)。

所谓的关联式容器,观念上类似关联式数据库:每笔数据(每个元素)都有一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部结构(可能是RB-tree ,也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素放置于适当位置。关联式容器没有所谓的头尾(只有最大元素和最小元素),所以不会有所谓push_back()/push_front()/pop_back()/pop_front()/begin()/end() 这样的操作行为。

一般而言,关联式容器的内部结构是一个balanced binary tree(平衡二叉树),以便获得良好的搜寻效率。balanced binary tree 有许多种类型,包括AVL-tree、RB-tree、AA-tree ,其中最被广泛运用于STL的是RB-tree(红黑树)。

二、树

有关树以及红黑树的相关内容,参见 红黑树详解 博文。 红黑树及其迭代器源码请参考《STL源码剖析》5.2节。

三、set

set 的特性是,所有元素都会根据元素的键值自动被排序。set 的元素不像map那样可以同时拥有实值和键值,set元素的键值就是实值,实值就是键值。set不允许两个元素有相同的键值。

务必注意,我们不能通过set的迭代器改变set的元素值。因为set元素值就是其键值,关系到set元素的排序规则。如果任意改变set元素值,会严重破坏set组织。在set的源码中,set<T>::iterator被定义为底层RB-tree 的const_iterator,杜绝写入操作。换句话说,set iterators 是一种constant iterators(相对于mutable iterators)。

STL特别提供了一组set、multiset 相关算法,包括交集set_intersection、并集set_union、差集set_difference、对称差集set_symmetric_difference。

由于RB-tree 是一种平衡二叉搜索树,自动排序的效果很不错,所以标准的STL set即以RB-tree为底层机制。又由于set 所开放的各种操作接口,RB-tree也都提供了,所以几乎所有的set操作行为,都只是转调用RB-tree 的操作行为而已。

参见相关源码;

四、map

map的特性是,所有元素都会根据元素的键值自动被排序。map的所有元素都是pair,同时拥有实值和键值。pair 的第一元素被视为键值,第二元素被视为实值。map不允许两个元素拥有相同的键值(但两个元素的实值可以相同)。同样,我们不能通过map的迭代器改变map的键值,但我们可以改变其实值。因此,map iterators 既不是一种constant iterators,也不是一种mutable iterators。map也是也是以RB-tree为底层机制,通过转调RB-tree的操作行为来实现自己的接口功能。

参见相关源码;

五、multiset

multiset 的特性以及用法和set 完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制RB-tree的insert_equal()而非insert_unique()——insert_equal()表示被插入节点的键值在整棵树中可以重复,因此,无论如何插入都会成功(除非空间不足导致配置失败)。insert_unique()表示被插入节点的键值key在整棵树中必须独一无二(因此,如果树中已存在相同的键值,插入操作就不会真正进行)。

参见相关源码;

六、multimap

multimap 的特性以及用法与map完全相同,唯一的差别在于它允许键值重复,因此它的插入操作采用的是底层机制RB-tree的insert_equal()而非insert_unique()。 参见相关源码;

参见附录1 SGI STL RB-tree的insert_equal()和insert_unique()插入源码

七、hashtable

参见 hashtable详解

八、hash_set

虽然STL 只规范复杂度与接口,并不规范实现方法,但STL set 多半以RB-tree 为底层机制。SGI 则是在STL 标准规格之外另又提供了一个所谓的hash_set,以hashtable 为底层机制。由于hash_set所供应的操作接口,hashtable都提供了,所以几乎所有的hash_set 操作行为,都只是转调用hashtable的操作行为而已。

运用set,为的是能够快速搜寻元素。这一点,不论其底层是RB-tree 或是hashtable,都可以达成任务。但是请注意,RB-tree 有自动排序功能而hashtable 没有,反映出来的结果就是,set的元素有自动排序功能而hash_set 没有。 hash_set 的使用方式,与set完全相同。

参见相关源码;

九、hash_map

SGI 在STL 标准规格之外,另提供了一个所谓的hash_map,以hashtable 为底层机制。由于hash_map 所供应的操作接口,hashtable 都提供了,所以几乎所有的hash_map操作行为,都只是转调用hashtable 的操作行为而已。

RB-tree 有自动排序功能而hashtable 没有,所以map 的元素有自动排序功能而hash_map没有。

参见相关源码;

十、hash_multiset

hash_multiset 的特性于multiset 完全相同。唯一的差别在于它的底层机制是hashtable。也因此,hash_multiset 的元素并不会被自动排序。

hash_multiset 和 hash_set 实现上的唯一差别在于,前者的元素插入操作采用底层机制hashtable的insert_equal(),后者则是采用 insert_unique()。

十一、hash_multimap

hash_multimap 的特性于multimap 完全相同。唯一的差别在于它的底层机制是hashtable。也因此,hash_multimap 的元素并不会被自动排序。

hash_multimap 和 hash_map 实现上的唯一差别在于,前者的元素插入操作采用底层机制hashtable的insert_equal(),后者则是采用 insert_unique()。

附录1:SGI STL RB-tree的insert_equal()和insert_unique()插入源码

// 插入新值:节点键值允许重复
// 注意,返回值是一个RB-Tree迭代器,指向新增节点
template <class _Key, class _Value, class _KeyOfValue,
class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
::insert_equal(const _Value& __v)
{
_Link_type __y = _M_header;
_Link_type __x = _M_root(); // 从根节点开始
while (__x != ) { // 从根节点开始,往下寻找适当的插入点
__y = __x;
// _M_key_compare(v, x) =意思是=> (x > v ? true : false)
__x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ?
_S_left(__x) : _S_right(__x);
// 新值遇到比它“大”的节点则往左,否则(遇到比它“小于或等于”的节点)则往右
}
return _M_insert(__x, __y, __v);
// 以上,x为新值插入点,y为插入点之父节点,v为新值
} // 插入新值:节点键值不允许重复,若重复则插入无效
// 注意,返回值是个pair,第一个元素是个RB-Tree迭代器,指向新增节点,
// 第二个元素表示插入成功与否
template <class _Key, class _Value, class _KeyOfValue,
class _Compare, class _Alloc>
pair<typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator,
bool>
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
::insert_unique(const _Value& __v)
{
_Link_type __y = _M_header;
_Link_type __x = _M_root(); // 从根节点开始
bool __comp = true;
while (__x != ) { // 从根节点开始,往下寻找适当的插入点
__y = __x;
__comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x));
// 如果节点值比新值大则往左;否则往右(注意,_KeyOfValue()(__v)为第1参数)
__x = __comp ? _S_left(__x) : _S_right(__x);
} //离开while循环之后,__y所指即插入点之父节点(此时的它必为叶节点) iterator __j = iterator(__y); // 令迭代器__j指向插入点之父节点__y
if (__comp) // 如果离开while循环时comp为真(表示插入节点值“大于”新值,将在左侧插入)
if (__j == begin()) // 如果插入点之父节点为最左节点
return pair<iterator,bool>(_M_insert(__x, __y, __v), true);
// 以上,x为插入点,y为插入点之父节点,v为新值
else // 否则(插入点之父节点不为最左节点)
--__j; // 调整__j (重新确定插入点,见附录2图)

// (1)__comp == false => 插入节点值“小于或等于”新值,右侧插入
// (2)__comp == true && 插入点之父节点不为最左节点 => 调整__j(重新确定插入点,见附录2图)
if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v)))
// 如果新值“大于”插入节点值((注意,_KeyOfValue()(__v)为第2参数))
return pair<iterator,bool>(_M_insert(__x, __y, __v), true);
// 以上,__x为新值插入点,__y为插入点之父节点,__v为新值 // 至此,表示新值一定与树中键值重复,那么就不该插入新值
return pair<iterator,bool>(__j, false);
}

附录2图:

注:准备插入v=20的值,此时RB-Tree里面已经有一个值为20的节点了。

遍历步骤已经插入流程如下(如图,红色箭头表示遍历路径):

1. 40节点比新值大,往左走;

2. 20节点不比新值大(“小于或等于”),往右走;

3. 30节点比新值大,往左走;同时,达到叶子节点;此时j指向30节点;

4. 30节点不是最左节点,执行“--j”,j新指向20节点;

5. 执行后续判断(也即进入上面代码(1)、(2)情况的语句):

  a. 新值是否大于j节点,如果是,插入;

  b. 否则(本例子的情况,“小于或等于”),不插入。

注:步骤2(20节点“小于或等于”新值)和步骤5(新值“小于或等于”20节点),由这两个判断可以推断:新值“等于”20节点。进而说明了原RB-Tree中已有相同的值,故而不再插入。

STL——关联式容器的更多相关文章

  1. 关联式容器(associative containers)

    关联式容器(associative containers) 根据数据在容器中的排列特性,容器可分为序列式(sequence)和关联式(associative)两种. 标准的STL关联式容器分为set( ...

  2. STL源码分析读书笔记--第5章--关联式容器

    1.关联式容器的概念 上一篇文章讲序列式容器,序列式容器的概念与关联式容器相对,不提供按序索引.它分为set和map两大类,这两大类各自有各自的衍生体multiset和multimap,的底层机制都是 ...

  3. STL学习笔记--关联式容器

    关联式容器依据特定的排序准则,自动为其元素排序.缺省情况下以operator<进行比较.set multiset map multimap是一种非线性的树结构,具体的说是采用一种比较高效的特殊平 ...

  4. C++ 容器:顺序性容器、关联式容器和容器适配器

    什么是容器 首先,我们必须理解一下什么是容器,在C++ 中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器.很简单,容器就是保存其它对象的对象 ...

  5. C++关联式容器的排序准则

    stl中set和map为关联式容器,会根据排序准将元素自动排序.原型如下: template<class _Kty, class _Pr = less<_Kty>, class _A ...

  6. STL关联式容器之set\map ----以STL源码为例

    关联式容器的特征:所用元素都会根据元素的键值自动被排序. set STL 中的关联式容器低层数据结构为红黑树,其功能都是调用低层数据结构中提供的相应接口. set元的元素不会像map那样同时拥有键(k ...

  7. Map(关联式容器)

    map是一类关联式容器,自动建立Key - Value的对应 , key 和 Value可以是任意你需要的类型.下面介绍 map 中一些常用的函数: 一.map 中的 begin 和 end 函数 m ...

  8. 7.7 C++基本关联式容器

    参考:http://www.weixueyuan.net/view/6404.html 总结: 基本的关联式容器主要有:set.multiset.map和multimap,这四种容器可以分为两组:ma ...

  9. STL序列式容器学习总结

    STL序列式容器学习总结 参考资料:<STL源码剖析> 参考网址: Vector: http://www.cnblogs.com/zhonghuasong/p/5975979.html L ...

随机推荐

  1. ThinkPHP使用memcache缓存服务器

    (1)Thinkphp的默认缓存方式是以File方式,在/Runtime/Temp 下生成了好多缓存文件. 服务器装了memcached后想给更改成memecache方式 在Conf/config.p ...

  2. thinkphp5的Redis缓存配置

    thinkphp采用cache类提供缓存功能支持,采用驱动方式,在使用缓存之前需要进行初始化操作.支持的缓存类型包括file.memcache.wincache.sqlite.redis和xcache ...

  3. 关于Unity中的特殊文件目录和资源管理(专题四)

    编辑器扩展目录: 1: Editor目录 Editor文件夹可以在根目录下,也可以在子目录里,夹就可以; Editor下面放的所有资源文件或者脚本文件都不会被打进发布包中,并且脚本也只能在编辑时使用; ...

  4. 字节码加载和class实例的顺序问题

    刷头条的时候看到了这个:  你做会错的一道Java面试题:字节码加载和class实例的顺序问题 以前也看到过,应该是阿里的校招笔试题,当时懒得理这种工作中毫无意义的东西. 今天突然来了兴趣,就想看看能 ...

  5. js方法传入对象;js方法传入方法;js方法回调 callback

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  6. 关于HTTP Message

    HTTP Message包括JS, HTML等Resource.这些都是相对来说有代码可以写的东西,但是原理的东西是没有代码的.coding只是很少的一部分工作内容. Browser的流程.比如con ...

  7. spark1.4配置安装

    https://segmentfault.com/a/1190000004508993

  8. 安卓开发笔记——TabHost组件(一)(实现底部菜单导航)

    什么是TabHost? TabHost组件的主要功能是可以进行应用程序分类管理,例如:在用户使用windows操作系统的时候,经常见到如图所示的图形界面.     TabHost选项卡,说到这个组件, ...

  9. 内存管理 初始化(五)kmem_cache_init 初始化slab分配器(上)

    看了下kmem_cache_init,涉及到不同MIGRATE间的buddy system的迁移,kmem_cache的构建,slab分配器头的构建.buddy system的伙伴拆分. 对于SMP系 ...

  10. VCL 中的 Windows API 函数(2): ActivateKeyboardLayout

    ActivateKeyboardLayout 分别在 Controls.DBGrids.Grids 单元用到, 基本都是如下语句: ActivateKeyboardLayout(Screen.Defa ...