虽然 GESP C++ 的大纲中并未提到 list、vector 和 deque,但在5级的选择题和编程题参考答案中经常会看到使用 vector 的代码。另外在5级的大纲中提到了链表数据结构,在6级的大纲中提到了队列数据结构。综合这些因素,我在《CCF GESP 直通车 —— C++精讲精练》5级中介绍了list、vector 和 deque,它们统称为序列容器类。
(GESP 是 CCF 推出的一个针对中、小学生编程能力的认证考试,正在被越来越多的家长接受和认可。本人也是根据多年的教学经验写成了《CCF GESP 直通车 —— C++精讲精练》系列丛书。)
从三个容器的基本操作来看,它们提供的功能非常相似:都支持在任意位置插入和添加元素,也都能够访问任意元素(尽管 list 没有提供直接的随机访问接口,但通过迭代器仍可实现逐个访问)。那么它们之间究竟有什么区别呢?
这三者最根本的区别之一在于它们的存储结构不同。list 的元素在内存中是完全非连续存储的,而 vector 则是整段连续存储的。相比之下,deque 采用了一种“分段连续”的存储方式。所谓分段连续,是指整个数据结构被划分为多个连续的内存段,段内是连续的,但段与段之间不一定连续。
它们的存储结构如下图所示:

从完全离散到整段连续,再到分段连续,这个过程其实很像我们家里存放书籍的方式。
假设你是一位图书爱好者,刚开始收藏了十几本书。这时你还没有买书架,书本东一本西一本地散落在各个角落——这就像是list的存储方式,彼此独立、随意分布。
随着藏书渐渐增加到几十本,你买了一个书架,把所有的书都整整齐齐地码放在上面。这就好比 vector,所有元素连续存储在一片内存中,访问起来非常方便。
书越来越多了,原来的书架已经装不下了。你希望仍然只用一个书架,于是买了一个更大的,把旧书架的书全部搬进新书架,再把旧书架处理掉——这正是 vector 的扩容机制:重新分配一整块更大的内存,将原有数据全部拷贝过去,再释放旧空间。
后来,你的大书架又满了。但房间空间有限,没法放下更大的书架了,你只好在别的房间又添置了几个书架。每个书架内部是连续的,但不同书架之间并不相邻——这就是 deque 的存储方式:分段连续,每段各自连续,段与段之间离散分布。
而当你继续买书时,只需再新增一个书架即可,不需要像 vector 那样把所有书重新搬一遍——这正是 deque 扩容的优势:不需要整体迁移数据,只需新增一个内存段。
它们的第二个区别在于各项操作的时间复杂度不同,具体差异如下表所示。这种性能差异主要源于它们各自的存储结构和扩容机制的不同。
|
操作 |
deque |
list |
vector |
|
头部插入/删除 |
O(1) |
O(1) |
O(n) |
|
尾部插入/删除 |
O(1) |
O(1) |
O(1)(分摊) |
|
中间插入/删除 |
O(n) |
O(1)(已知位置后) |
O(n) |
|
访问元素 |
O(1) |
O(n)(无随机访问迭代器) |
O(1) |
从存储结构的角度来看,deque 与 vector 更为接近。它们最显著的区别在于在头部进行插入或删除操作时的时间复杂度不同,其余方面的差异则相对较小。具体区别如表所示。
|
特性 |
vector |
deque |
|
内存布局 |
单一大块的连续内存 |
多个固定大小的连续内存块(分段) |
|
扩容方式 |
重新分配:分配一块更大的新内存,整体搬家。 |
增量分配:只需分配一个新的小块内存(头尾都可以),并记录到索引数组中。现有元素不动。 |
|
插入成本(尾部) |
分摊 O(1)(得益于几何增长) |
真正的 O(1)(几乎总是) |
|
插入成本(头部) |
O(n)(需要移动所有元素) |
O(1) |
|
随机访问 |
极快的 O(1)(一次指针计算) |
较快的 O(1)(两次指针计算:先找块,再找位置) |
|
内存使用 |
通常更紧凑,浪费少(只在 capacity > size 时浪费尾部空间) |
有额外开销(需要维护索引数组,每个内存块可能未满) |
如何选择
我们现在知道了这三个容器类的区别,那么我们到底应该如何选择呢?我们应该根据程序的需求来选择:
- list:如果你需要频繁在序列的任意已知位置进行插入和删除。
- vector:如果你需要频繁随机访问,且操作主要在尾部。
- deque:如果你主要需要在头尾操作,但偶尔需要在中间操作。
本文为学漄乐码堂主撰写。如果您想要学到真正的知识,而不只是应试的技巧,欢迎留言跟我联系。

被折叠的 条评论
为什么被折叠?



