STL源码剖析——关联式容器的总结

来源:互联网 发布:51秀啦网站源码 编辑:程序博客网 时间:2024/06/06 15:42
关联式容器分为set(集合)和map(映射0两大类,以及这两大类衍生体multiset(多键集合)和multimap(多键映射)。这些容器的底层机制均以RB-tree(红黑树)完成。RB-tree是一个独立的容器,并不开放给外界使用。
此外SGI STL还提供了一个不再标准规格之列的关联式容器:hash talbe(散列表),以及以此hash table 为底层机制而完成的hash_set(散列集合),hash_map(散列映射表),hash_multiset(散列多键集合),hash_multimap(散列多键映射表)。
hash table(散列表)及其衍生容器相当重要,它们未被纳入C++标准的原因是,提案太迟了。
关联式容器:每笔数据(每个元素)都有一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部结构(可能是RB-tree,也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素放置于适当位置。
一般而言,关联式容器内部结构是一个 balanced binary tree(平衡二叉树),以便获得良好的搜寻效率,其中包括:
AVL-tree(AVL树),RB-tree(红黑树),AA-tree,其中最被广泛运用于STL的是RB-tree。
5.1 树
5.1.1 二叉搜索树
二叉树应用:编译器表达式树,哈夫曼编码树。
二叉搜索树的节点放置规则是:任何节点的键值一定大于其左子树中的每一个节点的键值,并小于其右子树中的每一个节点的键值。
5.1.3 AVL-tree  :任何节点的左右子树高度相差最多 1
平衡调整:
调整“插入点至根节点”路径上,平衡状态被破坏之各节点中最深的那一个,便可使整棵树重新获得平衡。假设X为最深节点,由于节点最多拥有两个字节点,而所谓的“平衡被破坏”意味着X的左右两颗子树的高度相差2,因此我们可以轻易将情况分为四种(图5-9)
1、插入点位于X的左子节点的左子树——左左。
2、插入点位于X的左子节点的有子树——左右。
3、插入点位于X的右子节点的左子树——右左。
4、插入点位于X的右子节点的右子树——右右。

情况1,4彼此对称,成为外侧(outside)插入,可以采用单旋转操作(single rotation )调整解决,情况2,3彼此对称,成为内侧(inside)插入,可以采用双旋转操作(double rotation)调整解决。
 
STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
5.1.4单旋转(Single Rotation)
 STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
5.1.5 双旋转(Double Rotation)
STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客

  5.2 RB-tree (红黑树)
  RB-tree满足的规则:
1、每个节点不是红色就是黑色
2、根为黑色
3、如果节点为红,其子节点必须为黑
4、任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同。
STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
  
 
STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
 

5.2.7 RB-tree 的元素操作
两种插入操作:insert_unique():插入的键值在整棵树中必须独一无二。
insert_equal():被插入节点的键值在整棵树中可以重复。
5.3 set
set的特性是,所有元素都会根据元素的键值自动排序。set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。set不允许两个元素有相同的键值。
不可以通过set迭代器改变元素值。
由于RB-tree是一种平衡二叉搜索树,自动排序的效果很不错,所以set即以 RB-tree为底层机制。
插入操作采用的底层机制是RB-tree的insert_unique()。
multiset
特性和用法与set完全相同,唯一的差别是允许键值重复,插入操作采用的底层机制是RB-tree的insert_equal().
5.4 map
map的特性是,所有元素都会根据元素的键值自动被排序,map所有元素都是pair,同时拥有实值(value)和键值(key)。pair的第一元素被视为键值,第二元素被视为实值。map不允许两个元素拥有相同的键值。
不可以通过map迭代器修改键值,但是可以修改实值。
插入操作采用的底层机制是RB-tree的insert_unique()。
multimap
特性和用法与set完全相同,唯一的差别是允许键值重复,插入操作采用的底层机制是RB-tree的insert_equal().

5.7 hashtable(散列表)
二叉搜索树具有对数平均时间的表现,但这样的表现构造在一个假设上:输入数据有足够的随机性。
hashtable(散列表):这种结构在插入、删除、搜寻等操作上也具有“常数平均时间”的表现。而且这种表示是以统计为基础,不需依赖输入元素的随机性。
举个例子:如果所有元素都是16-bits且不带正负号的整数,范围0~65535,那么简单运用一个array就可以满足上述期望。
这个解法存在两个问题:第一,如果元素是31-bits,那么所准备的array 大小就是2^32 = 4GB ,这就大的不切实际了。
第二,如果元素型态是字符串(或其他)而非整数,将无法被拿来作为array的索引。
解决上述两个问题的方法就是:使用某种映射函数,将大数映射为小数,负责将某一元素映射为一个“大小可接受之索引”,这样的函数成为hash function (散列函数)。
解决hash函数碰撞的方法:
1、线性探测。
负载系数(loading factor),意指元素个数除以表格大小,负载系数拥有在0——1之间——除非采用开链策略。
 二次探测。
二次探测主要用来解决主集团(primary clustering)的问题,如果hash function计算出新元素的位置为H,而该位置实际上已被使用,那么我们就依序尝试H+1^2,H+2^2,H+2^3,……而不是像线性探测依序尝试H+1,H+2,H+3,……
STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
 幸运的是,如果我们假设表格大小为质数(prime),而且永远保持负载系数在0.5以下。
 STL源码剖析——关联式容器 - michaelkingno1 - 麦麦爱吃肉的博客
2、开链(separate chaining)
每一个表格元素中维护一个list;hash function 为我们分配某一个list,然后我们在那个list身上执行元素的插入、搜寻、删除等操作。虽然针对list而进行搜寻只能是一种线性操作,但如果list够短,速度还是够快。
5.8  hash_set 以hashtable 为底层机制。
hash_map 以hashtable 为底层机制。
  hash_multiset以hashtable 为底层机制。
  hash_multimap 以hashtable 为底层机制。
  这些以hashtable 为底层机制的容器的元素不会自动排序。