跳到主要内容C++ STL 容器适配器详解:Stack、Queue、Priority Queue 原理与实战 | 极客日志C++算法
C++ STL 容器适配器详解:Stack、Queue、Priority Queue 原理与实战
深入解析 C++ STL 容器适配器 Stack、Queue 与 Priority Queue。从接口使用到底层原理,探讨为何选择 deque 作为默认容器,剖析堆结构在优先队列中的应用。结合最小栈、逆波兰表达式及 Kth Largest Element 等经典 OJ 案例,提供完整的模拟实现思路与性能测试对比,助读者掌握数据结构核心逻辑与实战技巧。
人间过客1 浏览 C++ STL 容器适配器详解:Stack、Queue、Priority Queue
一、stack 的介绍和使用
1.1 stack 介绍
栈(Stack)是一种后进先出(LIFO)的数据结构。在 C++ STL 中,stack 被归类为容器适配器,它并不直接存储数据,而是基于其他容器(如 vector、deque、list)进行封装,提供特定的接口。

1.2 stack 的使用
| 函数说明 | 接口说明 |
|---|
| stack() | 构造空的栈 |
| empty() | 检测 stack 是否为空 |
| size() | 返回 stack 中元素的个数 |
| top() | 返回栈顶元素的引用 |
| push() | 将元素 val 压入 stack 中 |
| pop() | 将 stack 中尾部的元素弹出 |
1.3 stack 代码题
【最小栈】
基本思路
为了能在 O(1) 时间内获取最小值,我们可以使用两个栈:一个正常存储数据 _st,另一个专门存储当前状态下的最小值 _minst。

代码实现
class MinStack {
public:
MinStack() {}
void push(int val) {
_st.push(val);
if (_minst.empty() || val <= _minst.top()) {
_minst.(val);
}
}
{
(_st.() == _minst.()) {
_minst.();
}
_st.();
}
{
_st.();
}
{
_minst.();
}
:
stack<> _st;
stack<> _minst;
};
push
void pop()
if
top
top
pop
pop
int top()
return
top
int getMin()
return
top
private
int
int
【栈的压入弹出序列】
模拟入栈和出栈的过程。遍历压入序列,将元素压入辅助栈,然后检查栈顶是否与弹出序列当前元素匹配。如果匹配则弹出,直到不匹配或栈为空。
class Solution {
public:
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
size_t pushi = 0, popi = 0;
stack<int> st;
while (pushi < pushV.size()) {
st.push(pushV[pushi++]);
while (!st.empty() && st.top() == popV[popi]) {
st.pop();
popi++;
}
}
return popi == popV.size();
}
};
【逆波兰表达式求值】
遇到操作数就入栈,遇到操作符就弹出两个操作数进行计算,结果再入栈。
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for (auto& str : tokens) {
if (str == "+" || str == "-" || str == "*" || str == "/") {
int right = st.top(); st.pop();
int left = st.top(); st.pop();
if (str == "+") {
st.push(left + right);
} else if (str == "-") {
st.push(left - right);
} else if (str == "*") {
st.push(left * right);
} else {
st.push(left / right);
}
}
else {
st.push(stoi(str));
}
}
return st.top();
}
};
1.4 stack 的模拟实现
从接口可以看出,栈实际是一种特殊的线性结构。使用 vector 完全可以模拟实现 stack。用 list 也可以,取决于你传入的 Container 支持哪些接口。
#pragma once
#include <iostream>
#include <vector>
#include <list>
#include <deque>
using namespace std;
namespace lrq {
template<class T, class Container = deque<T>>
class stack {
public:
void push(const T& x) {
_con.push_back(x);
}
void pop() {
_con.pop_back();
}
size_t size() const {
return _con.size();
}
bool empty() const {
return _con.empty();
}
const T& top() const {
return _con.back();
}
private:
Container _con;
};
}
二、queue 的介绍和使用
2.1 queue 简介
队列(Queue)是一种先进先出(FIFO)的容器适配器。元素从队尾入队列,从队头出队列。底层容器可以是标准容器类模板之一(如 deque、list),默认情况下使用 deque。
empty():检测队列是否为空
size():返回队列中有效元素的个数
front():返回队头元素的引用
back():返回队尾元素的引用
push_back():在队列尾部入队列
pop_front():在队列头部出队列
2.2 queue 的使用
| 函数声明 | 接口说明 |
|---|
| queue() | 构造空的队列 |
| empty() | 检测队列是否为空 |
| size() | 返回队列中有效元素的个数 |
| front() | 返回对头元素的引用 |
| back() | 返回队尾元素的引用 |
| push() | 在队尾将元素 val 入队列 |
| pop() | 将队头元素出队列 |
2.3 queue 的模拟实现
注意:queue 不可以使用 vector 作为底层来实现,因为 vector 不支持头删(pop_front)操作。
#pragma once
namespace lrq {
template<class T, class Container = deque<T>>
class queue {
public:
void push(const T& x) {
_con.push_back(x);
}
void pop() {
_con.pop_front();
}
size_t size() const {
return _con.size();
}
const T& front() const {
return _con.front();
}
bool empty() const {
return _con.empty();
}
private:
Container _con;
};
}
三、priority_queue 的介绍和使用
3.1 priority_queue 简介
优先队列(Priority Queue)是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素。
底层容器可以是任何标准容器类模板(如 vector、deque)。默认情况下,如果没有指定容器类,则使用 vector。需要支持随机访问迭代器,以便始终在内部保持堆结构。
3.2 priority_queue 的使用
优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将元素构造成堆的结构。注意:默认情况下 priority_queue 是大堆。
#include <iostream>
#include <queue>
using namespace std;
int main() {
priority_queue<int, vector<int>, greater<int>> pq;
pq.push(1);
pq.push(10);
pq.push(90);
pq.push(4);
while (!pq.empty()) {
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
return 0;
}
3.3 在 OJ 题中的应用
【数组中的第 K 个最大元素】
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> pq(nums.begin(), nums.end());
while (--k) {
pq.pop();
}
return pq.top();
}
};
3.4 priority_queue 的模拟实现
通过对 priority_queue 的底层结构就是堆,因此此处只需对堆进行通用的封装即可。
数据结构关于二叉树的忘了真得复习了,不然下面看起来费劲。
#pragma once
#include <vector>
namespace bit {
template <class T>
struct less {
bool operator()(const T& x, const T& y) const {
return x < y;
}
};
template <class T>
struct greater {
bool operator()(const T& x, const T& y) const {
return x > y;
}
};
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue {
public:
priority_queue() = default;
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last) :_con(first, last) {
for (int i = (_con.size() - 1 - 1) / 2; i >= 0; i--) {
AdjustDown(i);
}
}
void AdjustUp(int child) {
Compare com;
int parent = (child - 1) / 2;
while (child > 0) {
if (com(_con[parent], _con[child])) {
swap(_con[child], _con[parent]);
child = parent;
parent = (parent - 1) / 2;
} else {
break;
}
}
}
void push(const T& x) {
_con.push_back(x);
AdjustUp(_con.size() - 1);
}
void AdjustDown(int parent) {
Compare com;
size_t child = parent * 2 + 1;
while (child < _con.size()) {
if (child + 1 < _con.size() && com(_con[child], _con[child + 1])) {
++child;
}
if (com(_con[parent], _con[child])) {
swap(_con[child], _con[parent]);
parent = child;
child = parent * 2 + 1;
} else {
break;
}
}
}
void pop() {
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
AdjustDown(0);
}
bool empty() {
return _con.empty();
}
const T& top() {
return _con[0];
}
size_t size() {
return _con.size();
}
private:
Container _con;
};
}
四、容器适配器
4.1 什么是适配器
适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。
4.2 STL 标准库中 stack 和 queue 的底层结构
虽然 stack 和 queue 中也可以存放元素,但在 STL 中并没有将其划分在容器的行列,而是将其称为容器适配器。这是因为 stack 和队列只是对其他容器的接口进行了包装。STL 中 stack 和 queue 默认使用 deque。
4.3 deque 的简单介绍
【deque 的原理介绍】
double-ended queue(双端队列):是一种双开口的"连续"空间的数据结构。双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为 O(1)。与 vector 比较,头插效率高,不需要搬移元素;与 list 比较,空间利用率比较高。
deqque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个动态的二维数组。
【deque 的缺陷】
与 vector 比较,deque 的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率比 vector 高。
与 list 比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。
但是,deque 有一个致命缺陷:不适合遍历。因为在遍历时,deque 的迭代器要频繁地去检测其是否移动到某段小空间的边界,导致效率低下。因此在实际中,需要线性结构时,大多数情况下优先考虑 vector 和 list,deque 的应用并不多,而目前能看到的的一个应用就是,STL 用其作为 stack 和 queue 的底层数据结构。
4.4 为什么选择 deque 作为 stack 和 queue 的底层默认容器
stack 是一种后进先出的特殊线性数据结构,只要具有 push_back() 和 pop_back() 操作的线性结构都可以作为底层容器,比如 vector 和 list。
queue 是先进先出的特殊线性数据结构,只要具有 push_back 和 pop_front 操作的线性结构都可以作为底层容器,比如 list。
但是 STL 中对 stack 和 queue 默认选择 deque 作为其底层容器,主要是因为:
stack 和 queue 不需要遍历(因此没有迭代器),只需要在固定的一端或者两端进行操作。
- 在
stack 中元素增长时,deque 比 vector 的效率高(扩容时不需要搬移大量数据);queue 中的元素增长时,deque 不仅效率高,而且内存使用率高。
下面是本节课对 STL 库里面实现的 stack 和 queue 进行测试以及对 vector 和 deque 排序效率的测试。
#include <stack.h>
#include <queue.h>
#include <vector>
#include <deque>
#include <iostream>
#include <algorithm>
using namespace std;
void test_op1() {
srand(time(0));
const int N = 1000000;
deque<int> dq;
vector<int> v;
for (int i = 0; i < N; ++i) {
auto e = rand() + i;
v.push_back(e);
dq.push_back(e);
}
int begin1 = clock();
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
sort(dq.begin(), dq.end());
int end2 = clock();
printf("vector:%d\n", end1 - begin1);
printf("deque:%d\n", end2 - begin2);
}
void test_op2() {
srand(time(0));
const int N = 1000000;
deque<int> dq1;
deque<int> dq2;
for (int i = 0; i < N; ++i) {
auto e = rand() + i;
dq1.push_back(e);
dq2.push_back(e);
}
int begin1 = clock();
sort(dq1.begin(), dq1.end());
int end1 = clock();
int begin2 = clock();
vector<int> v(dq2.begin(), dq2.end());
sort(v.begin(), v.end());
dq2.assign(v.begin(), v.end());
int end2 = clock();
printf("deque sort:%d\n", end1 - begin1);
printf("deque copy vector sort, copy back deque:%d\n", end2 - begin2);
}
int main() {
test_op2();
return 0;
}
同样的排序,vector 比 deque 要快。这验证了之前提到的 deque 遍历效率低下的问题。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online