deque - 双端队列
std::deque 是双端队列(double-ended queue)的缩写,支持在两端进行高效的插入和删除操作。在算法竞赛中,deque 是实现滑动窗口等高级算法的核心工具。
竞赛中的核心考点
| 操作 | 时间复杂度 | 竞赛要点 |
|---|
| 两端插入/删除 | 均摊 O(1) | 高效的两端操作 |
| 随机访问 | O(1) | 支持索引访问 |
| 中间插入/删除 | O(n) | 避免在中间操作 |
| 大小操作 | O(1) | 快速获取大小和判空 |
一、基本用法
1.1 初始化
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
deque<int> dq;
deque<int> dq1(5);
deque<int> dq2(5, 10);
vector<int> v = {1, 2, 3, 4, 5};
deque<int> dq3(v.begin(), v.end());
deque<int> dq4(dq3);
deque<int> dq5(move(dq4));
return 0;
}
1.2 基本操作
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
deque<int> dq;
dq.push_back(10);
dq.push_front(20);
dq.emplace_back(30);
dq.emplace_front(40);
dq.pop_back();
dq.pop_front();
cout << "队首: " << dq.front() << endl;
cout << "队尾: " << dq.back() << endl;
cout << "索引1: " << dq[1] << endl;
cout << "索引1: " << dq.at(1) << endl;
cout << "大小: " << dq.size() << endl;
cout << "是否为空: " << (dq.empty() ? "是" : "否") << endl;
dq.clear();
cout << "清空后大小: " << dq.size() << endl;
return 0;
}
二、滑动窗口算法
2.1 滑动窗口最大值
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
vector<int> max_sliding_window(vector<int>& nums, int k)
{
vector<int> result;
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)
{
result.push_back(nums[dq.front()]);
}
}
return result;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
vector<int> result = max_sliding_window(nums, k);
cout << "滑动窗口最大值: ";
for (int x : result)
{
cout << x << " ";
}
cout << endl;
return 0;
}
2.2 滑动窗口最小值
vector<int> min_sliding_window(vector<int>& nums, int k)
{
vector<int> result;
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)
{
result.push_back(nums[dq.front()]);
}
}
return result;
}
三、竞赛实战应用
3.1 双端队列实现栈和队列
class MyStack
{
private:
deque<int> dq;
public:
void push(int x)
{
dq.push_back(x);
}
int pop()
{
int x = dq.back();
dq.pop_back();
return x;
}
int top()
{
return dq.back();
}
bool empty()
{
return dq.empty();
}
};
class MyQueue
{
private:
deque<int> dq;
public:
void push(int x)
{
dq.push_back(x);
}
int pop()
{
int x = dq.front();
dq.pop_front();
return x;
}
int front()
{
return dq.front();
}
bool empty()
{
return dq.empty();
}
};
3.2 0-1 BFS
vector<int> zero_one_bfs(int start, const vector<vector<pair<int, int>>>& adj)
{
int n = adj.size();
vector<int> dist(n, INT_MAX);
deque<int> dq;
dist[start] = 0;
dq.push_front(start);
while (!dq.empty())
{
int u = dq.front();
dq.pop_front();
for (const auto& edge : adj[u])
{
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
if (w == 0)
{
dq.push_front(v);
}
else
{
dq.push_back(v);
}
}
}
}
return dist;
}
3.3 滑动窗口中位数
vector<double> median_sliding_window(vector<int>& nums, int k)
{
vector<double> result;
multiset<int> window(nums.begin(), nums.begin() + k);
auto mid = next(window.begin(), k / 2);
for (int i = k; ; i++)
{
if (k % 2 == 0)
{
result.push_back((*mid + *prev(mid)) / 2.0);
}
else
{
result.push_back(*mid);
}
if (i == nums.size())
{
break;
}
window.insert(nums[i]);
if (nums[i] < *mid)
{
mid--;
}
if (nums[i - k] <= *mid)
{
mid++;
}
window.erase(window.lower_bound(nums[i - k]));
}
return result;
}
四、C++11 现代特性
4.1 使用 pair 元素访问
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
deque<pair<int, string>> dq;
dq.emplace_back(1, "apple");
dq.emplace_back(2, "banana");
for (const auto& p : dq)
{
int id = p.first;
string name = p.second;
cout << "id: " << id << ", name: " << name << endl;
}
return 0;
}
4.2 使用标准算法
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
deque<int> dq = {3, 1, 4, 1, 5, 9, 2, 6};
sort(dq.begin(), dq.end());
auto it = find(dq.begin(), dq.end(), 5);
if (it != dq.end())
{
cout << "找到 5 在位置: " << distance(dq.begin(), it) << endl;
}
cout << "偶数: ";
for (int x : dq)
{
if (x % 2 == 0)
{
cout << x << " ";
}
}
cout << endl;
return 0;
}
五、性能优化
5.1 预分配空间
int expected_size = 100000;
deque<int> dq;
dq.reserve(expected_size);
deque<int> dq(expected_size);
5.2 避免中间操作
deque<int> dq = {1, 2, 3, 4, 5};
dq.insert(dq.begin() + 2, 10);
dq.push_front(0);
dq.push_back(6);
5.3 合理选择容器
| 容器 | 优点 | 缺点 | 适用场景 |
|---|
| deque | 两端操作高效,支持随机访问 | 内存碎片,中间操作慢 | 滑动窗口,双端操作 |
| vector | 缓存友好,随机访问快 | 两端操作慢 | 随机访问频繁,大小稳定 |
| list | 中间插入删除快 | 不支持随机访问,缓存不友好 | 频繁中间操作 |
六、常见错误与注意事项
| 错误 | 说明 | 解决方案 |
|---|
| 越界访问 | 访问超出范围的索引 | 使用 at() 或先检查大小 |
| 中间插入删除 | 导致性能下降 | 优先在两端操作 |
| 内存使用过高 | deque 内存碎片 | 考虑使用 vector 或 list |
| 迭代器失效 | 插入删除操作后迭代器失效 | 注意保存有效的迭代器 |
| 滑动窗口逻辑错误 | 窗口边界处理不当 | 仔细检查窗口大小和边界条件 |
七、C++11 完整竞赛模板
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
vector<int> max_sliding_window(vector<int>& nums, int k)
{
vector<int> result;
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)
{
result.push_back(nums[dq.front()]);
}
}
return result;
}
vector<int> zero_one_bfs(int start, const vector<vector<pair<int, int>>>& adj)
{
int n = adj.size();
vector<int> dist(n, INT_MAX);
deque<int> dq;
dist[start] = 0;
dq.push_front(start);
while (!dq.empty())
{
int u = dq.front();
dq.pop_front();
for (const auto& edge : adj[u])
{
int v = edge.first;
int w = edge.second;
if (dist[v] > dist[u] + w)
{
dist[v] = dist[u] + w;
if (w == 0)
{
dq.push_front(v);
}
else
{
dq.push_back(v);
}
}
}
}
return dist;
}
class MyStack
{
private:
deque<int> dq;
public:
void push(int x)
{
dq.push_back(x);
}
int pop()
{
int x = dq.back();
dq.pop_back();
return x;
}
int top()
{
return dq.back();
}
bool empty()
{
return dq.empty();
}
};
class MyQueue
{
private:
deque<int> dq;
public:
void push(int x)
{
dq.push_back(x);
}
int pop()
{
int x = dq.front();
dq.pop_front();
return x;
}
int front()
{
return dq.front();
}
bool empty()
{
return dq.empty();
}
};
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector<int> nums = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
vector<int> result = max_sliding_window(nums, k);
cout << "滑动窗口最大值: ";
for (int x : result)
{
cout << x << " ";
}
cout << endl;
int n = 4;
vector<vector<pair<int, int>>> adj(n);
adj[0] = {{1, 0}, {2, 1}};
adj[1] = {{0, 0}, {3, 1}};
adj[2] = {{0, 1}, {3, 0}};
adj[3] = {{1, 1}, {2, 0}};
vector<int> dist = zero_one_bfs(0, adj);
cout << "0-1 BFS 距离: ";
for (int d : dist)
{
cout << d << " ";
}
cout << endl;
MyStack stack;
stack.push(1);
stack.push(2);
stack.push(3);
cout << "栈顶: " << stack.top() << endl;
cout << "弹出: " << stack.pop() << endl;
cout << "栈顶: " << stack.top() << endl;
MyQueue queue;
queue.push(1);
queue.push(2);
queue.push(3);
cout << "队首: " << queue.front() << endl;
cout << "弹出: " << queue.pop() << endl;
cout << "队首: " << queue.front() << endl;
return 0;
}
总结:竞赛要点
- 双端操作:deque 支持在两端进行高效的插入和删除操作
- 滑动窗口:deque 是实现滑动窗口算法的最佳选择
- 随机访问:支持 O(1) 的随机访问,比 list 更灵活
- 0-1 BFS:利用双端队列优化边权为 0 或 1 的图的最短路径
- 数据结构实现:可以用 deque 实现栈和队列
- 性能优化:优先在两端操作,避免中间插入删除
- 现代 C++11:使用 auto 类型推导、范围 for 循环和 emplace 原地构造
- 边界处理:注意迭代器失效和越界访问
- 实战应用:滑动窗口最大值、最小值、中位数等
deque 是算法竞赛中处理需要双端操作场景的强大工具,掌握它的使用技巧能让你在解决滑动窗口等问题时更加得心应手。