C++笔记——STL deque
1. deque 基本概念
什么是deque?
双端队列:可以在头部和尾部进行高效插入删除的动态数组
核心特性(与vector对比):
| 特性 | vector | deque |
|---|---|---|
| 随机访问 | ✅ O(1) | ✅ O(1) |
| 尾部操作 | ✅ O(1) | ✅ O(1) |
| 头部操作 | ❌ O(n) | ✅ O(1) |
| 中间操作 | ❌ O(n) | ❌ O(n) |
| 内存布局 | 单块连续 | 多块连续(分段数组) |
2. deque 的基本使用

头文件和声明:
cpp
#include <deque>
using namespace std;// 各种创建方式(与vector几乎相同)
deque<int> dq1; // 空deque
deque<int> dq2(10); // 10个元素,默认值0
deque<int> dq3(5, 100); // 5个元素,每个都是100
deque<int> dq4 = {1, 2, 3, 4, 5}; // 初始化列表
deque<int> dq5(dq4); // 拷贝构造3. deque 的特有操作 - 头部操作
这是与vector最大的区别!
cpp
void deque_special_operations() {deque<int> dq = {2, 3, 4};// 头部操作 - vector没有这些!dq.push_front(1); // 头部插入: {1,2,3,4}dq.pop_front(); // 头部删除: {2,3,4}dq.emplace_front(0); // 头部原地构造: {0,2,3,4}// 尾部操作(与vector相同)dq.push_back(5); // 尾部插入: {0,2,3,4,5}dq.pop_back(); // 尾部删除: {0,2,3,4}cout << "头部元素: " << dq.front() << endl; // 0cout << "尾部元素: " << dq.back() << endl; // 4
}4. deque 的容量管理
与vector的重要区别:
cpp
void deque_capacity() {deque<int> dq = {1, 2, 3, 4, 5};// 这些与vector相同:cout << "大小: " << dq.size() << endl; // 5cout << "是否为空: " << dq.empty() << endl; // false// 这些与vector不同:// dq.capacity(); // ❌ deque没有capacity()方法!// dq.reserve(100); // ❌ deque没有reserve()方法!// dq.shrink_to_fit();// ❌ deque没有shrink_to_fit()方法!// 但有resize(与vector相同)dq.resize(10); // 扩容到10个元素,新增元素默认初始化dq.resize(3); // 缩容到3个元素
}关键理解: deque的内存管理是自动的,不需要手动控制容量。
5. deque 的元素访问
访问方式与vector完全相同:
cpp
void deque_access() {deque<int> dq = {10, 20, 30, 40, 50};// 所有访问方式都与vector一样cout << dq[2] << endl; // 30 - 下标访问cout << dq.at(2) << endl; // 30 - 安全访问cout << dq.front() << endl; // 10 - 头部元素cout << dq.back() << endl; // 50 - 尾部元素// 数据指针(但用处不大,因为内存不连续)// int* data = dq.data(); // ❌ deque没有data()方法!
}6. deque 的修改操作
与vector相同的操作:
cpp
void deque_modifications() {deque<int> dq = {1, 2, 3, 4, 5};// 这些与vector相同:dq.insert(dq.begin() + 2, 99); // 中间插入: {1,2,99,3,4,5}dq.erase(dq.begin() + 1); // 删除指定位置: {1,99,3,4,5}dq.clear(); // 清空所有元素// emplace系列也相同dq.emplace(dq.begin(), 100); // 中间原地构造dq.emplace_back(200); // 尾部原地构造dq.emplace_front(300); // 头部原地构造
}7. deque 的迭代器
使用方式与vector相同,但实现原理不同:
cpp
void deque_iterators() {deque<int> dq = {10, 20, 30, 40, 50};// 迭代器用法与vector完全一样for (auto it = dq.begin(); it != dq.end(); ++it) {cout << *it << " "; // 正向遍历}for (auto rit = dq.rbegin(); rit != dq.rend(); ++rit) {cout << *rit << " "; // 反向遍历}// 随机访问迭代器(与vector相同)auto it = dq.begin();cout << it[2] << endl; // 30 - 支持随机访问it = it + 3; // 支持算术运算
}8. deque 的内部实现原理
这才是deque与vector的根本区别!
vector的内存布局:
text
vector: [元素1][元素2][元素3][元素4][元素5]...↑单块连续内存
deque的内存布局(分段数组):
text
deque: [指针1] → [元素1][元素2][元素3] ← 第一个内存块 [指针2] → [元素4][元素5][元素6] ← 第二个内存块 [指针3] → [元素7][元素8][元素9] ← 第三个内存块↑中控器(指针数组)
这种设计的优势:
cpp
void deque_advantages() {deque<int> dq;// 1. 头部插入不会导致元素大范围移动for (int i = 0; i < 1000; i++) {dq.push_front(i); // 高效!只在当前内存块操作// vector的push_front需要移动所有元素!}// 2. 不需要大块连续内存// vector需要找到足够大的连续内存块// deque可以分散在多块较小的内存中
}9. 性能对比测试
头部操作性能对比:
cpp
#include <chrono>
using namespace std::chrono;void performance_comparison() {const int COUNT = 100000;// vector头部插入auto start1 = high_resolution_clock::now();vector<int> vec;for (int i = 0; i < COUNT; i++) {vec.insert(vec.begin(), i); // 每次都要移动所有元素!}auto end1 = high_resolution_clock::now();// deque头部插入auto start2 = high_resolution_clock::now();deque<int> dq;for (int i = 0; i < COUNT; i++) {dq.push_front(i); // 高效头部插入}auto end2 = high_resolution_clock::now();auto vec_time = duration_cast<milliseconds>(end1 - start1);auto deque_time = duration_cast<milliseconds>(end2 - start2);cout << "vector头部插入时间: " << vec_time.count() << "ms" << endl;cout << "deque头部插入时间: " << deque_time.count() << "ms" << endl;
}结果: deque的头部操作比vector快几个数量级!
10. deque 的典型应用场景
适合使用deque的场景:
1. 滑动窗口问题
cpp
void sliding_window(const vector<int>& nums, int k) {deque<int> dq; // 存储索引for (int i = 0; i < nums.size(); i++) {// 从头部移除超出窗口的元素if (!dq.empty() && dq.front() == i - k) {dq.pop_front();}// 从尾部移除较小的元素while (!dq.empty() && nums[dq.back()] < nums[i]) {dq.pop_back();}dq.push_back(i);// 输出当前窗口最大值if (i >= k - 1) {cout << "窗口最大值: " << nums[dq.front()] << endl;}}
}2. 任务队列
cpp
class TaskQueue {
private:deque<function<void()>> tasks;mutex mtx;public:// 高优先级任务插到头部void addHighPriorityTask(function<void()> task) {lock_guard<mutex> lock(mtx);tasks.push_front(move(task));}// 普通任务放到尾部void addTask(function<void()> task) {lock_guard<mutex> lock(mtx);tasks.push_back(move(task));}// 从头部取任务执行void executeNext() {lock_guard<mutex> lock(mtx);if (!tasks.empty()) {auto task = move(tasks.front());tasks.pop_front();task();}}
};3. 撤销操作栈
cpp
class UndoRedoSystem {
private:deque<string> history;size_t max_size;public:UndoRedoSystem(size_t max = 100) : max_size(max) {}void addAction(const string& action) {history.push_back(action);// 保持历史记录不超过最大大小if (history.size() > max_size) {history.pop_front(); // 移除最旧的操作}}string undo() {if (history.empty()) return "";string action = history.back();history.pop_back();return action;}
};11. deque 的注意事项
1. 内存局部性比vector差
cpp
void memory_locality() {vector<int> vec(1000);deque<int> dq(1000);// vector的内存访问更连续,缓存命中率更高for (int i = 0; i < 1000; i++) {vec[i] = i; // 连续内存访问,缓存友好}for (int i = 0; i < 1000; i++) {dq[i] = i; // 可能跨越不同内存块,缓存不友好}
}2. 迭代器比vector复杂
cpp
void iterator_complexity() {deque<int> dq = {1, 2, 3, 4, 5};auto it = dq.begin();// 虽然接口相同,但内部实现更复杂it++; // 可能需要检查是否跨越内存块边界it = it + 10; // 计算更复杂,需要确定目标内存块
}
12. 总结
选择指南:deque vs vector
使用 deque 当:
✅ 需要频繁在头部和尾部插入删除
✅ 作为队列或双端队列使用
✅ 内存碎片化严重,找不到大块连续内存
使用 vector 当:
✅ 主要进行尾部操作
✅ 需要最高性能的随机访问
✅ 重视缓存局部性
✅ 需要手动控制内存分配
性能对比总结:
| 操作 | vector | deque |
|---|---|---|
| 尾部插入删除 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 头部插入删除 | ⭐ | ⭐⭐⭐⭐⭐ |
| 随机访问 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 内存效率 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
一句话总结:
deque = vector的随机访问 + list的高效头尾操作 - 连续内存的优势
deque在特定场景下是非常有用的工具,理解它的内部原理有助于在合适的时候选择它!
