vector VS deque

1. vector与deque

vector与动态数组相同,能够在插入或删除元素时自动调整自身大小,其存储由容器自动处理,vector通常占用多于静态数组的空间,因为要分配更多的内存以管理将来的增长,在每次插入元素的时,仅当额外内存耗尽时触发重新分配。

如上图所示,vector元素放置在连续存储中,以便可以使用迭代器访问和遍历他们。在vector中,末尾插入需要不同的时间,因为有时候需要扩展存储空间。对于删除最后一个元素,因为不涉及存储空间大小的调整,则执行时间是恒定的。对于开头或者中间插入和擦除在时间上是线性的,因为可能要涉及到元素的移动。

deque是具有两端扩缩功能的序列容器。其存储方式与vector相反,deque的元素不是相接存储的,是由一段一段等长的连续空间构成的,各段之间并不一定是连续的。它的典型实现如下图所示,通过单独分配固定尺寸的序列(对应图中的数据区),外加额外的登记(对应图中map映射区),map数组中存储的是每段连续空间的地址,通过映射区来管理这些一段一段等长的连续空间,进而实现"整体连续"的效果。

deque容器需要在头部或者尾部增加空间的时候,它会申请一段新的连续空间,同时在map数组的开头或者结尾添加指向该空间的指针,由此将deque元素串接起来。在遇到空间不足的时候由于deque可以申请新的连续空间,原数据空间可以保持不变,更新map即可,所以deque在涉及到空间扩展的时候,效率远高于vector

2. 性能比较

2.1 随机访问

由于vector是连续存储的,deque是分段连续存储,其随机访问需对map数组进行二次指针解引用(可以理解为:deque随机访问需要先去找到待访问元素在哪段连续存储空间,然后再对该空间进行下标访问),而vector只有下标访问一次即可。因此在随机访问性能上,vector略高于deque,但两者复杂度均为常数 O ( 1 ) O(1) O(1)。

2.2 末尾插入/删除

前面我们说过,vector的存储是自动管理的,按需扩张收缩,vector通常占用多于静态数组的空间,因为要分配更多的内存以管理将来的增长,通常情况下vector在尾部插入元素的复杂度为 O ( 1 ) O(1) O(1),但当额外内存耗尽的时候,需要重新分配,此时重新分配,是需要单独再申请一份更大空间,把vector原有的元素重新放到新申请的空间上,再完成尾部插入,此时涉及到了新空间的申请、所有元素的移动和旧空间的释放,这种情况下的插入在性能上是灾难级别的,因此,总的来说对于vector尾部插入的时间复杂度为均摊常数 O ( 1 ) O(1) O(1)

对于deque由于存储空间是分段连续的,当空间不够的时候重新申请新的一段空间即可,不会涉及到旧元素的移动,其复杂度度为常数 O ( 1 ) O(1) O(1)。对于尾部删除,因为不涉及到分配空间申请,因此两者的复杂度均在 O ( 1 ) O(1) O(1)。

vector在尾部插入元素的时,新的size()如果大于capacity(),那么所有的迭代器和引用(包括end()迭代器)都会失效,否则只有end()迭代器会失效。而deque除了迭代器会失效,而不会使指向其余元素的指针或引用失效。

2.3 随机插入/删除

vector在进行随机插入的时候,涉及到插入位置到序列尾部这段元素的移动(可以理解为这段元素需要整体往后移动一位,给新插入元素把位置留出来),随机删除元素同理,因此其随机插入/删除的时间复杂度为插入位置与到vector尾部距离成线性 O ( n ) O(n) O(n)。 deque的扩展方式是双向的,因此其可以**根据插入位置距离头部或者尾部较近的距离成线性 O ( n ) O(n) O(n),**因此,其性能略胜vector一丢丢。

:对于vector随机插入,如果新size()大于旧的capacity()就会导致重分配,所有的迭代器和引用都会失效,否则,只有在插入点前的迭代器和引用会保持有效。对于deque而言,所有迭代器和引用也会失效,除非插入位置为容器尾部或者头部,引用不会失效。

3. 总结

vectordeque的对比如下表所示:

vector deque
头文件 使用需要包含头文件<vector> 使用需要包含头文件<deque>
存储方式 连续存储元素 包含元素连续存储的内存快列表
随机访问 支持,复杂度为常数 O ( 1 ) O(1) O(1) 支持,复杂度为常数 O ( 1 ) O(1) O(1)
末尾插入/末尾删除元素 均摊常数 O ( 1 ) O(1) O(1) 常数 O ( 1 ) O(1) O(1)
随机插入/随机删除元素 与到vector结尾的距离成线性 O ( n ) O(n) O(n) 线性 O ( n ) O(n) O(n)

vector重分配在性能上是有开销的,如果在使用之前元素的数量已知,那么可以使用rederve()函数来消除重分配。

deque的存储按需自动扩展及收缩,扩展deque比扩张vector更优,因为它不涉及到复制既存元素到新内存位置。但另外一方面,deque典型地拥有较大的最小内存开销,所以当即使保有一个元素的时候,deque也需要为它分配它的整个内部数组。

文章首发公众号:iDoitnow如果喜欢话,可以关注一下

相关推荐
躺不平的理查德10 分钟前
Windows C++ 第三方库使用流程备忘录--OpenCV
开发语言·c++
余额瞒着我当琳17 分钟前
C++STL容器string--迭代器,string的接口,string的遍历,访问方式
c++
郭涤生18 分钟前
rootfs 详解与裁剪优化记录
linux·c++·bsp
小小龙学IT1 小时前
C++ std::vector 底层实现深度解析:内存布局、扩容策略、移动语义与迭代器失效
开发语言·c++·算法
码匠许师傅2 小时前
【C++ 面试真题】聊聊 volatile 和 mutable
开发语言·c++·面试
(Charon)2 小时前
【C++】线程安全队列(二):生产者消费者模型与 condition_variable 阻塞等待
c语言·开发语言·c++
10岁的博客3 小时前
【C++题解】最多共线点问题:小飞拿蛋糕游戏的最优策略
c++
東隅已逝,桑榆非晚3 小时前
类和对象(中)
c++·笔记
wuminyu4 小时前
JDK21解决虚拟线程IO阻塞原理剖析
java·linux·c语言·jvm·c++
MC皮蛋侠客4 小时前
TDengine C++ 系列(4):时序查询——窗口聚合、插值、连接与时序函数
大数据·c++·tdengine