以下是 C++ 中 std::deque(双端队列)的详细解释,涵盖其底层实现、核心特性、常用操作及适用场景:
1. 基本概念
定义:
std::deque(Double-Ended Queue)是一种双端动态数组,支持在头部和尾部高效插入/删除元素。头文件:
#include <deque>核心特性:
两端操作高效:头部和尾部插入/删除的时间复杂度为 O(1)。
支持随机访问:通过下标直接访问元素(类似
vector)。分段连续存储:元素分布在多个固定大小的内存块中,通过映射表管理(非完全连续,但逻辑上连续)。
2. 底层实现
分段存储结构:
deque由多个内存块(chunks)组成,每个块存储部分元素。通过一个中央控制器(通常是数组或
vector)管理这些块的地址。内存分布示例:
[块1] → [块2] → [块3]
元素逻辑上连续,但物理上可能分散在不同内存块中。
扩展机制:
当头部或尾部空间不足时,动态分配新内存块。
无需像
vector那样整体搬迁数据,两端插入效率更高。
3. 初始化与构造
(1) 空容器
std::deque<int> dq; // 空deque
(2) 指定大小和初始值
std::deque<int> dq1(5); // 5个元素,默认初始化为0 std::deque<int> dq2(5, 42); // 5个元素,初始化为42
(3) 列表初始化(C++11+)
std::deque<int> dq = {1, 2, 3, 4, 5};
std::deque<std::string> names {"Alice", "Bob"};(4) 从其他容器构造
std::vector<int> vec {10, 20, 30};
std::deque<int> dq3(vec.begin(), vec.end()); // 复制vec的元素
std::deque<int> dq4(dq3); // 拷贝构造4. 元素访问
(1) 随机访问(下标)
int val1 = dq[2]; // 不检查越界(类似数组) int val2 = dq.at(3); // 越界抛出std::out_of_range异常
(2) 首尾元素
int first = dq.front(); // 首元素 int last = dq.back(); // 尾元素
(3) 迭代器遍历
// 正向遍历
for (auto it = dq.begin(); it != dq.end(); ++it) {
std::cout << *it << " ";
}
// 范围for循环(C++11+)
for (const auto& num : dq) {
std::cout << num << " ";
}5. 添加/删除元素
(1) 两端操作
dq.push_front(0); // 头部插入元素 dq.emplace_front(-1); // 更高效(C++11+,避免拷贝) dq.push_back(6); // 尾部插入元素 dq.emplace_back(7); // 直接构造元素 dq.pop_front(); // 删除头部元素 dq.pop_back(); // 删除尾部元素
(2) 任意位置插入/删除
auto it = dq.begin() + 2; dq.insert(it, 99); // 在位置2插入99(时间复杂度 O(n)) dq.erase(it); // 删除位置2的元素(时间复杂度 O(n))
6. 容量管理
int size = dq.size(); // 元素数量 bool isEmpty = dq.empty(); // 是否为空 dq.resize(10); // 调整大小,多出元素默认初始化 dq.resize(15, 42); // 调整到15个元素,新增元素初始化为42
7. 性能分析
(1) 时间复杂度
| 操作 | 时间复杂度 |
|---|---|
头部插入/删除(push_front/pop_front) | O(1) |
尾部插入/删除(push_back/pop_back) | O(1) |
中间插入/删除(insert/erase) | O(n) |
随机访问(operator[]) | O(1)(但比 vector 慢) |
(2) 与 vector 对比
| 特性 | std::deque | std::vector |
|---|---|---|
| 内存布局 | 分段连续 | 完全连续 |
| 头部插入效率 | O(1) | O(n)(需移动所有元素) |
| 随机访问速度 | 稍慢(需计算块和偏移) | 极快 |
| 扩容机制 | 动态添加内存块 | 整体搬迁数据 |
| 迭代器失效 | 插入可能使所有迭代器失效 | 扩容时全部失效 |
8. 适用场景
高频两端操作:如实现队列(FIFO)或双端队列。
需随机访问但无法预知大小:避免
vector扩容时的性能损失。避免中间插入/删除:与
vector类似,中间操作效率低。
9. 示例代码
#include <deque>
#include <iostream>
int main() {
std::deque<int> dq {2, 3, 4};
dq.push_front(1); // 头部插入1 → [1, 2, 3, 4]
dq.emplace_back(5); // 尾部插入5 → [1, 2, 3, 4, 5]
// 删除中间元素(效率低)
auto it = dq.begin() + 2;
dq.erase(it); // 删除3 → [1, 2, 4, 5]
// 随机访问
std::cout << dq[1] << std::endl; // 输出2
// 遍历输出
for (int num : dq) {
std::cout << num << " "; // 输出:1 2 4 5
}
return 0;
}10. 注意事项
迭代器失效:
在中间插入/删除元素会导致所有迭代器失效。
在两端插入通常不会使其他位置的迭代器失效(除非触发内存块重新分配)。
内存碎片:分段存储可能导致内存碎片,影响缓存局部性。
性能取舍:若需要高频中间操作,优先选择
std::list;若需要连续内存,选择std::vector。
11. 总结
优势:高效的两端操作 + 随机访问。
劣势:中间操作效率低,内存非完全连续。
设计哲学:在
vector和list之间权衡,适用于特定场景(如滑动窗口算法、任务调度等)。
系统当前共有 481 篇文章