stl容器
vector
size()函数返回当前vector所容纳元素的数目,即使用的空间大小
capacity()函数返回当前vector在重新进行内存分配以前所能容纳的元素数量,即返回的是总的容量大小,capacity()-size()后就是未使用的空间大小。
push元素时, 是增加size()的大小, 当size()==capacity()时, 触发扩容
使用者可以通过reserve()来改变capacity(),resize()改变size()。
resize,即重置容器空间。当设置值小于当前容器空间时,会将目前容器中超出设置值的空间释放掉;当设置值大于当前容器空间时,会在当前空间的基础上增加容量。
vector扩容
不同编辑器扩容因子不一样, vs 为 1.5, gcc 为 2
push_back的话,一般来说,都是按两倍来扩容,因为push_back每次都是只插入一个数据
insert的话,因为可以一次插入多个数据,所以要复杂一些。
触发扩容时,如果要插入的数据量比旧容量小,则按两倍扩容;
如果要插入的数据量比原来的旧容量还要大,会按照旧容量加要插入的数据量来扩容,保证一次扩容就能容下要插入的数据;
即 new_capacity = max ( old_capacity * 2, n + old_capacity )
扩容因子
从空间上来分析,扩容因子越大,意味着预留空间越大,浪费的空间也越多,所以从空间考虑,扩容因子因越小越好。
从时间上来分析,如果预留空间不足的话,就需要重新开辟一段空间,把原有的数据复制到新空间,如果扩容因子无限大的话,那显然就不再需要额外开辟空间了。所以时间角度看,扩容因子越大越好
不考虑一次性插入大量数据的情况,只考虑N次push_back()
总消耗 = 每次插入消耗+ 扩容消耗
每次插入消耗
显然每次常规插入操作需要1的时间,插入n个数据的常规时间为N
扩容消耗
现在考虑额外的消耗,扩容因子为K,意味着一共需要扩容$\log_kN$,为简化计算,假设是容量是从1开始,也就是要对下面的等比序列求和
S = 1 + k + $k^2$ + $k^3$ + $k^4$ + …… + $k^{log_kN}$= (N-1)/(K-1)
显然,也就意味着扩容因子K越大,额外的消耗越小。总消耗为N+(N-1)/(K-1) 。当K大于1时,K越大总消耗越小。
顺便可以再求一下push_back的摊还时间复杂度。即为 (N+(N-1)/(K-1))/N = (NK-1)/(K-1)
最普遍的情况下,当扩容因子为2时,最好的评价时间复杂度为2N,发生在N等于2的n次幂时,最差为3N,发生在N等于2的n次幂加1时
k=2与k=1.5的比较
K = 2时
每次扩容后capacity的情况如下:1,2,4,8,16,32 ……..
当我们释放了4的空间,我们寻找8的新空间,再次扩容,释放8,寻找16。。
仔细分析,第5次扩容时,需要寻找16的新空间,第4次释放了8,第3次释放了4,第2次释放了2,第1次释放了1,所以 1 + 2 + 4 + 8 = 15 < 16,也就意味着,之前释放的空间,永远无法被下一次的扩容利用,这对内存与cache是非常不友好的。
K = 1.5时
每次扩容之后capacity的情况为:1,2,3,4,6,9,13,19,28 ……
再按刚才的思路分析一遍,1 + 2 >= 3; 3 + 4 >= 6; 6 + 9 >= 13 …….
所以,当K为1.5时,显然对内存和cache要友好很多,至少从容量上来说,是存在重复利用的可能性的。
因此,我们可以得出结论,当K = 2时,时间上要比 K = 1.5 占优,而空间上比 1.5 稍有劣势。
那么,那个最佳的数是多少呢?
继续刚才的分析,我们希望的是,上几次的空间,存在被下一次扩容时利用的可能性。
也就是 X(n-2) + X(n-1) >= X(n),显然我们也希望时间上也要更好,即X(n-2) + X(n-1) = X(n)
即:1,2,3,5,8,13,21,34,55 。。。。
是不是很熟悉。。。是的,这就是我们的斐波那契数列。。。
那么当N趋于无限大时,取极限,最佳的扩容因子也就是那个最美的数,黄金分割率,1.618
如何避免扩容
对于构造开销比较大的类, vector的频繁扩容开销会很大
因为每次扩容都会重新调用构造函数
所以需要使用reserve()函数为vector预留空间, 来避免使用过程中的频繁扩容
list
list是基于双向链表,内存是不连续的,不支持下标访问,支持指针访问
deque
deque是一个double-ended queue,它具有以下两个特点:
支持[]操作符,也就是支持随即存取,并且和vector的效率相差无几
支持在两端的操作:push_back,push_front,pop_back,pop_front等,并且在两端操作上与list的效率也差不多
map和unordered_map
| map | unordered_map |
|---|---|
| 内部用红黑树实现,具有自动排序功能 | 内部用哈希表实现,内部元素无序杂乱 |
| 查询删除等操作复杂度为$o(logN)$ | 查找速度非常快(适用于大量的查询操作) |
| 占用空间,红黑树里每个节点需要保存父子节点和红黑性质等信息,空间占用较大 | 建立哈希表比较耗时 |
注意:
随着内部元素越来越多,两种容器的插入删除查询操作的时间都会逐渐变大,效率逐渐变低。
使用[]查找元素时,如果元素不存在,两种容器都是创建一个空的元素;
如果存在,会正常索引对应的值。
所以如果查询过多的不存在的元素值,容器内部会创建大量的空的键值对,后续查询创建删除效率会大大降低。
查询容器内部元素的最优方法是:先判断存在与否( count() ),再索引对应值(适用于这两种容器)
set
set底层也是红黑树, 它和map不同之处在于map有一个key值
multimap和multiset
可以有重复的值
multiset可以使插入的元素有序
stack和queue
有被问到过stack的底层是啥, 没回答上来
是deque捏~
注意: stack和queue都没有迭代器