【C++】STL之deque
deque
Deque 的底层既不直接依赖 vector
也不依赖 list
,而是结合了两者的思想,采用了一种分块(chunk)存储与动态指针数组(map)结合的结构。以下是详细分析:
1. 底层结构设计
Deque 的核心设计是分块存储 + 动态指针数组(map):
-
分块存储:
Deque 的元素被分散存储在多个固定大小的连续内存块(称为buffer
或chunk
)中。- 每个块的容量固定(例如 512 字节或存储固定数量的元素,如 16 个)。
- 块之间通过指针连接,但物理内存不连续(类似链表),但块内部是连续的(类似数组)。
-
中央控制器(map):
Deque 使用一个动态数组(类似vector
)管理这些块的指针,称为map
。map
本身是一个指针数组,每个元素指向一个块的起始地址。map
可以双向扩容(头部或尾部插入新块的指针),但通常实现中会预留前后空间,减少频繁扩容。
2. 为什么不是 vector
或 list
?
(1) 与 vector
的区别
-
内存连续性:
vector
要求所有元素在物理内存上连续,而 Deque 的块内连续,块间不连续。- Deque 的头尾插入可以快速分配新块,无需移动已有元素。
vector
的头部插入需要整体移动元素,效率低(O(n)
)。
-
扩容策略:
vector
扩容时需要重新分配更大的内存并复制所有元素(O(n)
)。- Deque 只需在
map
中插入新块指针(分摊O(1)
)。
(2) 与 list
的区别
-
内存局部性:
list
的每个元素单独分配内存(节点),空间开销大且访问效率低。- Deque 的块内连续存储,缓存友好,随机访问效率远高于
list
。
-
扩容方式:
list
每次插入只需分配一个节点(O(1)
)。- Deque 的块是预分配的,只有当块用满时才分配新块,减少内存碎片。
3. Deque 的核心优势
- 高效的头尾插入:
头尾插入只需操作map
的前后指针,或分配新块,时间复杂度为分摊O(1)
。 - 较好的随机访问:
通过map
快速定位元素所在的块,再通过块内偏移访问元素,时间复杂度O(1)
。 - 内存效率:
分块设计减少大规模数据复制的开销,同时保留局部连续性。
4. 实现细节示例(以 C++ STL 为例)
在 C++ 标准库的实现中:
map
是类似vector
的动态数组,但支持双向扩展。- 当
map
空间不足时,会重新分配更大的内存,将旧指针复制到新map
的中间位置,预留前后空间。
- 当
- 每个块(buffer)是独立分配的数组,大小通常为
512 字节
或固定元素数量。
5. 总结:何时选择 Deque?
- 适用场景:
- 频繁在头尾插入/删除元素(如队列或栈)。
- 需要中等频率的随机访问(优于
list
,但弱于vector
)。
- 不适用场景:
- 需要绝对的内存连续性(如与 C 接口交互时只能用
vector
)。 - 频繁在中间位置插入/删除(此时
list
或树结构更优)。
- 需要绝对的内存连续性(如与 C 接口交互时只能用
Deque 的底层设计是一种折中方案,结合了数组(块内连续)和链表(块间松散连接)的优点,同时通过动态指针数组(map)高效管理块,因此既不直接依赖 vector
也不依赖 list
。