C++ STL中的 Deque
顺序容器的底层数据结构及典型使用场景:
| 顺序容器 | 底层数据结构 | 使用场景 |
|---|---|---|
vector | 动态数组(连续内存空间) | - 需要频繁随机访问元素 - 主要在尾部进行插入/删除操作 - 对内存连续性有要求(如与C API交互) |
deque | 分段连续空间(中央控制器 + 多个缓冲区) | - 需要在头尾两端进行高效的插入/删除 - 相比 vector,中间插入更慢,但头尾操作更快 |
list | 双向链表(非连续内存) | - 需要在任意位置频繁插入/删除元素 - 不需要随机访问(或随机访问极少) - 对元素移动操作(如 splice)有较高要求 |
forward_list | 单向链表 | - 场景与list类似,但只需单向遍历- 对内存开销特别敏感(比 list省一个指针)- 不支持反向迭代器,不适合需要反向遍历的场景 |
array | 静态数组(连续内存,大小固定) | - 需要在编译期确定元素个数 - 不涉及动态扩容 - 需要最小的内存开销和最高的访问效率 |
这些容器各有特点,选择使用时通常根据随机访问需求、插入/删除的位置以及内存布局要求来决定。
Deque 是 Double-Ended Queue(双端队列)的缩写。它是一种序列(顺序)容器,允许你高效地从前端(front) 和 后端(back) 添加或删除元素。
语法
std::deque 类模板定义在 <deque> 头文件中。
deque<T> d;
其中:
| 符号 | 含义 |
|---|---|
T | 双端队列中元素的数据类型 |
d | 为双端队列指定的名称 |
基本操作
双端队列的基本操作如下所示:
1.插入元素
在双端队列中,有两种插入操作:push_back() 和 push_front()。
push_back():
- 在双端队列的
末尾添加一个元素 - 适用于追加数值
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d;
// Adding elements at the back
d.push_back(10);
d.push_back(20);
d.push_back(30);
// Displaying elements
cout << "Elements in deque (added using push_back): ";
for (int val : d) {
cout << val << " ";
}
cout << endl;
return 0;
}
Elements in deque (added using push_back): 10 20 30
push_front():
- 在双端队列的 前端(头部) 添加一个元素
- 帮助在 开头位置 高效地插入数值
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d;
// Adding elements at the front
d.push_front(30);
d.push_front(20);
d.push_front(10);
// Displaying elements
cout << "Elements in deque (added using push_front): ";
for (int val : d) {
cout << val << " ";
}
cout << endl;
return 0;
}
Elements in deque (added using push_front): 10 20 30
2.删除操作
在双端队列中,有两种删除操作:pop_back() 和 pop_front()。
2.1 pop_back()
- 移除双端队列中的最后一个元素
- 当你想从尾部删除时使用
2.2 pop_front()
- 移除双端队列中的第一个元素
- 从头部删除的高效方式
示例代码:
#include <deque>
#include <iostream>
using namespace std;
int main() {
deque<int> dq = {10, 20, 30, 40};
// 初始: [10, 20, 30, 40]
dq.pop_front(); // 移除头部: [20, 30, 40]
dq.pop_back(); // 移除尾部: [20, 30]
for (int val : dq) {
cout << val << " ";
}
// 输出: 20 30
return 0;
}
注意: 调用 pop_back() 或 pop_front() 之前,建议先用 empty() 检查队列是否为空,避免对空队列进行操作导致未定义行为。
3.访问元素
双端队列中的元素可以通过 front() 和 back() 进行访问。
3.1 front()
- 返回第一个元素
- 不会移除该元素
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d = {100, 200, 300};
// 访问前端元素
cout << "第一个元素(front)是:" << d.front() << endl;
return 0;
}
第一个元素(front)是:100
3.2 back()
- 返回最后一个元素
- 适用于检查末尾的值
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d = {10, 20, 30, 40};
// 访问最后一个元素
cout << "最后一个元素(back)是:" << d.back() << endl;
return 0;
}
最后一个元素(back)是:40
4.Size 和 Empty
4.1 size()
- 返回元素的数量
- 有助于循环遍历或检查长度
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d = {5, 10, 15, 20};
// 获取双端队列的大小
cout << "双端队列中的元素数量是:" << d.size() << endl;
return 0;
}
双端队列中的元素数量是:4
4.2 empty()
- 如果双端队列为空,则返回 true
- 在访问元素前用于错误检查非常有用
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d;
if (d.empty()) {
cout << "双端队列为空。" << endl;
} else {
cout << "双端队列不为空。" << endl;
}
// 添加一个元素后再次检查
d.push_back(100);
if (d.empty()) {
cout << "双端队列为空。" << endl;
} else {
cout << "双端队列不为空。" << endl;
}
return 0;
}
双端队列为空。
双端队列不为空。
5.清空操作(Clear)
- 移除双端队列中的所有元素
- 执行后双端队列变为空
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> d = {1, 2, 3, 4, 5};
cout << "清空前,大小:" << d.size() << endl;
// 清空双端队列中的所有元素
d.clear();
cout << "清空后,大小:" << d.size() << endl;
if (d.empty()) {
cout << "双端队列现在为空。" << endl;
}
return 0;
}
清空前,大小:5
清空后,大小:0
双端队列现在为空。
时间负责度
| 操作 | 时间复杂度 |
|---|---|
| 在末尾插入(Insert at back) | O(1) 均摊 |
| 在前端插入(Insert at front) | O(1) 均摊 |
| 在任意位置插入(Insert at arbitrary position) | O(n) |
| 从末尾删除(Remove from back) | O(1) 均摊 |
| 从前端删除(Remove from front) | O(1) 均摊 |
| 从任意位置删除(Remove from arbitrary position) | O(n) |
| 使用索引访问任意位置的元素 | O(1) |
| 使用索引更新任意位置的元素 | O(1) |
| 遍历双端队列(Iterate the deque) | O(n) |
Queue vs Deque
| 特性 | 队列(Queue) | 双端队列(Deque / Double-Ended Queue) |
|---|---|---|
| 定义 | 遵循 FIFO(先进先出)原则的线性数据结构 | 队列的通用版本,允许从两端进行插入和删除 |
| 允许的操作 | 入队(Enqueue,添加到尾部) 出队(Dequeue,从头部移除) | 前端插入(Insert Front) 尾部插入(Insert Rear) 前端删除(Delete Front) 尾部删除(Delete Rear) |
| 访问方式 | 受限:只能在尾部插入,在头部删除 | 更灵活:可以在前端和尾部进行插入和删除 |
| 使用场景 | 需要严格 FIFO 顺序时 (例如:任务调度) | 需要同时具备 FIFO 和 LIFO 行为时 (例如:滑动窗口问题、回文检查) |
| 效率 | 更简单,但功能有限 | 稍微复杂一些,但更强大 |
| 类型 | 简单队列 循环队列 优先队列 | 输入受限双端队列(仅尾部插入) 输出受限双端队列(仅头部删除) |