STL Containers

这里介绍下stl中的肉器。

容器分类

STL容器分两种:序列式容器,关联式容器。
未标题-1副本
上图以内缩方式来表达基层与衍生层的关系。
heap内含一个vector,priority-queue内含一个heap、stack和queue都含一个deque,set/map/multiset/multimap都内含一个RB-tree,hash_x都内含一个hastable。

序列式容器

所谓序列式容器,其中的元素都可序(ordered),但未必有序(sorted)。C++语言本身提供了一个序列式容器array。STL另外再提供 上列呈现的序列式容器。

vector

vector的数据安排以及操作方式与array非常相似。两者唯一差别在于空间的运用的灵活性上。array是静态空间,设定要先定义空间。vector是动态空间,它的内部机制会自行扩充空间以容纳新元素。vector人运用对于内存的合理利用与运用的灵活性有很大的帮助。
vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。这里具体要看空间配置的策略。

vector的数据结构

线性连续空间。它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求量更大一些,以备将来可能的扩充。这便是容量(capacity)的观念。
它以两个迭代器start和finish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。
捕获

vector的迭代器

因为普通指针就可以满足vector的所有必要条件,而vector支持随机存取,所以vector提供的是Random Access Iterators。

list

list的好处是每次插入或删除一个元素,就配置或释放一个元素空间。list对于空间的运用有绝对的精准,一点也不浪费。而且对于任何位置的元素插入或无素移除,list永远是常数时间。
list和vector的选择最多视所元素的多寡、元素的构造复杂度、元素存取行为的特性而定。

list的数据结构

从list的节点结构看,STL中的list是个双向链表。

list的迭代器

所其操作上看,其迭代器是Bidirectional Itertors。

deque

vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。
deque没有容量观念。不像vector那样“因旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”。
deque的数据结构:array无法成长,vector虽可成长,却只能向尾端成长,而且是个假象。deque采用一块空间作缓冲区,利用这个空间成长。
捕获

deque的迭代器

和vector相似,是Random Access Iterators。

stack与queue

这两种都没有迭代器,数据结构是用deque实现的(其实用list也可以)。

为什么选择deque作为stack、queue的底层默认容器?
stack是后进先出的特殊线性数据结构,只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front()操作的线性结构,都可以作为queue的底层容器,比如list
但是STL中对stack和queue选择deque作为其底层容器,主要是因为:
(1)stack和queue不需要遍历(stack和queue没有迭代器),只需要在固定的一端或者两端进行操作
(2)在stack中元素增长时,deque比vector的效率高
(3)在queue中的元素增长时,deque不仅效率高,而且内存使用率也高

heap

heap不归属于stl容器组件,它是priority queue的助手。priority queue允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先权最高(也就是数值最高)的元素开始取。binary max heap正是具有这样的特性,适合作为priority queue的底层机制。

heap底部容器

以vector表现的完全二叉树。实现算法为最大堆。

priority_queue
priority_queue是一个拥有权值观念的queue。
priority_queue没有迭代器。

slist

这是个单向链表,所以迭代器变为Forward Iterator。

关联式容器

标准的STL关联式容器分为set(集合)和map(映射表)两大类,以及这两大类的衍生体mutiset(多键集合)和multimap(多键映射表)。由于RB-tree自动排序的效果很不错,所以这些容器的底层机制均以RB-tree(红黑树)完成。RB-tree也是一个独立容器,但并不开放给外界使用。
此外,STL还提供了一个关联式容器:hash_table(散列表),以及以此为底层机制而完成的hash_set(散列集合)、hash_map(散列映射表)、hash_multiset(散列多键集合)、hash_multimap(散列多键映射表)。

所谓关联式容器,观念上类似关联式数据库(key-value)。当元素被插入到关联式容器中时,容器内部结构(可能是RB-tree,也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素放置于适当位置。
关联式容器没有所谓头尾(只有最大元素与最小元素)。
一般而言,关联式容器的内部结构是一个balanced binary tree,以便获得良好的搜寻效率。

set

set的特性是所有元素都会根据元素的键值自动被排序。set元素的键值就是实值,实值就是健值。set不允许两个元素有相同的键值。

map

map的特性是,所有元素都会根据元素的键值自动被排序。map的所有元素都是pair。

multiset与multimap

特性以及用法和各自兄弟(set,map)完全相同,唯一的差别在于它们允许键值重复。

hashtable

二叉搜索树表现的构造在一个假设上:输入数据有足够的随机性。而hash_table(散列表)的数据结构的操作表现是以统计为基础,不需仰赖输入元素的随机性。
hash_set、hash_map、hash_multiset、hash_multimap
都是以hash_table为底层机制。但其实元素不能自动排序。

Hash与RB树的区别

权衡三个因素: 查找速度, 数据量, 内存使用,可扩展性,有序性。
总体来说,hash查找速度会比RB树快,而且查找速度基本和数据量大小无关,属于常数级别;而RB树的查找速度是log(n)级别。并不一定常数就比log(n) 小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash。但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么一定要小心,hash可能会让你陷入尴尬,特别是当你的hash对象特别多时,你就更无法控制了,而且 hash的构造速度较慢。

红黑树并不适应所有应用树的领域。如果数据基本上是静态的,那么让他们待在他们能够插入,并且不影响平衡的地方会具有更好的性能。如果数据完全是静态的,例如,做一个哈希表,性能可能会更好一些。
在实际的系统中,例如,需要使用动态规则的防火墙系统,使用红黑树而不是散列表被实践证明具有更好的伸缩性。Linux内核在管理vm_area_struct时就是采用了红黑树来维护内存块的。
红黑树是有序的,Hash是无序的,根据需求来选择。

拿红黑树实现的Map和Hash实现的HashMap相比:
如果只需要判断Map中某个值是否存在之类的操作,当然是Hash实现的要更加高效。
如果是需要将两个Map求并集交集差集等大量比较操作,就是红黑树实现的Map更加高效。


El amor es una cosa esplendorosa…hasta que te sorprende tu esposa.