目录
[1 stack 栈](#1 stack 栈)
[1.1 栈概念](#1.1 栈概念)
[1.2 stack 常用成员接口](#1.2 stack 常用成员接口)
[1.3 OJ 经典栈例题](#1.3 OJ 经典栈例题)
[例题 1:最小栈 MinStack](#例题 1:最小栈 MinStack)
[例题 2:栈的压入、弹出序列](#例题 2:栈的压入、弹出序列)
[例题 3:逆波兰表达式求值](#例题 3:逆波兰表达式求值)
[1.4 stack 模拟实现(容器适配器)](#1.4 stack 模拟实现(容器适配器))
[2 queue 队列](#2 queue 队列)
[2.1 队列概念](#2.1 队列概念)
[2.2 queue 接口](#2.2 queue 接口)
[2.3 queue 模拟实现](#2.3 queue 模拟实现)
[3 priority_queue 优先级队列(堆)](#3 priority_queue 优先级队列(堆))
[3.1 概念](#3.1 概念)
[3.2 priority_queue 接口](#3.2 priority_queue 接口)
[示例 1:内置类型,大堆、小堆](#示例 1:内置类型,大堆、小堆)
[示例 2:自定义类型放入 priority_queue](#示例 2:自定义类型放入 priority_queue)
[OJ 例题:数组第 K 个最大元素](#OJ 例题:数组第 K 个最大元素)
[3.3 priority_queue 模拟实现](#3.3 priority_queue 模拟实现)
[4 容器适配器](#4 容器适配器)
[4.1 适配器模式设计思想](#4.1 适配器模式设计思想)
[4.2 deque 双端队列底层原理(了解)](#4.2 deque 双端队列底层原理(了解))
[4.3 为什么 stack 和 queue 默认选择 deque 作为底层?](#4.3 为什么 stack 和 queue 默认选择 deque 作为底层?)
[4.4 stack、queue 模板模拟完整版本(支持自定义底层容器)](#4.4 stack、queue 模板模拟完整版本(支持自定义底层容器))
本节学习目标
- 理解栈 stack 后进先出 LIFO 特性,掌握常用接口;会写最小栈、栈的压入弹出序列、逆波兰表达式
- 理解队列 queue 先进先出 FIFO,掌握接口,理解底层容器适配器
- 掌握
priority_queue优先级队列(堆),分清大堆、小堆,自定义类型比较规则,OJ 找第 K 大元素 - 搞懂容器适配器模式 ,理解为什么 STL stack/queue 默认底层选用
deque双端队列 - 了解 deque 双端队列底层结构、优缺点
- 看懂 stack、queue 模板模拟实现代码
1 stack 栈
1.1 栈概念

栈:后进先出 LIFO(Last‑In‑First‑Out) 。 只能在栈顶 top进行插入(push 入栈)、删除(pop 出栈)操作。
push():入栈,元素放到栈的顶部pop():出栈,删除栈顶元素top():获取栈顶元素,不会删除元素
类比弹夹:后压进去的子弹,最先打出去。
1.2 stack 常用成员接口
头文件:#include <stack>
表格
| 函数 | 功能说明 |
|---|---|
stack<T> st; |
构造空栈对象 |
empty() |
判断栈是否为空,空返回 true,非空 false |
size() |
返回栈中有效元素个数 |
top() |
返回栈顶元素引用,栈空调用会报错 |
push(val) |
元素 val 入栈,压到栈顶 |
pop() |
删除栈顶元素;无返回值!栈空调用会报错 |
⚠️注意:
pop()只删除,不返回栈顶元素 ;要拿栈顶先调用top()。- 调用
top()、pop()前建议先!st.empty()判空,否则程序崩溃。
示例基础使用
cpp
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> st;
st.push(10);
st.push(20);
st.push(30);
cout << st.size() << endl; //3
cout << st.top() << endl; //30
st.pop();
cout << st.top() << endl; //20
while(!st.empty())
{
cout << st.top() << " ";
st.pop();
}
return 0;
}
1.3 OJ 经典栈例题
例题 1:最小栈 MinStack
需求:实现栈,支持 push、pop、top,还可以
getMin()快速获取栈内最小值。 思路:双栈方案
_elem:保存全部真实元素_min:专门保存最小值序列;入栈的时候,如果新元素小于等于_min 栈顶,就压入_min;出栈的时候,如果出栈元素等于_min 栈顶,_min 也要同步 pop。
cpp
#include<stack>
class MinStack
{
public:
void push(int x)
{
_elem.push(x);
// 最小栈为空 或者x小于等于当前最小值,压入最小栈
if(_min.empty() || x <= _min.top())
{
_min.push(x);
}
}
void pop()
{
// 如果出栈的是当前最小值,最小栈也要弹出
if(_elem.top() == _min.top())
{
_min.pop();
}
_elem.pop();
}
int top()
{
return _elem.top();
}
int getMin()
{
return _min.top();
}
private:
stack<int> _elem;
stack<int> _min;
};
例题 2:栈的压入、弹出序列
输入两个序列:pushV 入栈序列,popV 出栈序列,判断 popV 是否是合法的出栈顺序。 模拟思路:
- 遍历 popV 出栈序列;
- 如果栈为空 或者栈顶不等于当前要出栈的元素,就持续把 pushV 元素压栈;
- 如果 pushV 全部压完,栈顶还不等于 popV 当前元素 → 不合法返回 false;
- 相等就 pop,移动出栈下标;全部处理完成返回 true。
cpp
#include <stack>
#include <vector>
using namespace std;
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
if(pushV.size() != popV.size())
return false;
stack<int> s;
int outIdx = 0; //出栈序列下标
int inIdx = 0; //入栈序列下标
while(outIdx < popV.size())
{
//栈空 或者栈顶不等于要弹出的元素,持续入栈
while(s.empty() || s.top() != popV[outIdx])
{
if(inIdx < pushV.size())
{
s.push(pushV[inIdx++]);
}
else
{
//入栈全部用完,还不匹配,非法
return false;
}
}
//栈顶匹配,执行出栈
s.pop();
outIdx++;
}
return true;
}
};
例题 3:逆波兰表达式求值
逆波兰(后缀表达式),遇到数字压栈;遇到运算符,弹出右操作数、左操作数,计算后结果重新压回栈。 注意:先 pop 出来是右操作数!顺序不能写反。
cpp
#include <stack>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
for(size_t i = 0; i < tokens.size(); ++i)
{
string& str = tokens[i];
if(str != "+" && str != "-" && str != "*" && str != "/")
{
//数字,转int压栈
s.push(atoi(str.c_str()));
}
else
{
//运算符:先弹出右操作数,再弹出左操作数
int right = s.top();
s.pop();
int left = s.top();
s.pop();
int res;
switch(str[0])
{
case '+': res = left + right; break;
case '-': res = left - right; break;
case '*': res = left * right; break;
case '/': res = left / right; break;
default: res = 0;
}
s.push(res);
}
}
return s.top();
}
};
课后练习 OJ:用两个栈实现队列。
1.4 stack 模拟实现(容器适配器)
stack 本身不存储数据,复用其他容器接口。可以用vector/deque/list作为底层。
vector 的
push_back、pop_back、back()刚好可以实现栈全部功能。
cpp
#include<vector>
namespace bite
{
template<class T>
class stack
{
public:
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_back(); }
T& top() { return _c.back(); }
const T& top()const { return _c.back(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
std::vector<T> _c;
};
}
2 queue 队列
2.1 队列概念

队列:先进先出 FIFO First‑In‑First‑Out。
- 队尾 back:入队列 push ();
- 队头 front:出队列 pop ()。
排队买票:先排队的人,先处理离开。
queue 属于容器适配器 ,底层默认使用deque双端队列。 底层容器必须支持:empty、size、front、back、push_back、pop_front。
list、deque 都满足要求;vector 不支持高效头删 pop_front,不能做 queue 底层。
2.2 queue 接口
头文件#include <queue>
表格
| 函数 | 功能说明 |
|---|---|
queue<T> q; |
构造空队列 |
empty() |
判空,空返回 true |
size() |
返回元素个数 |
front() |
获取队头元素引用,不删除 |
back() |
获取队尾元素引用,不删除 |
push(val) |
队尾入队列 |
pop() |
删除队头元素,无返回值 |
⚠️pop () 只删除队头,不返回;取队头必须调用 front ();操作前建议判空。
基础示例:
cpp
#include <iostream>
#include <queue>
using namespace std;
int main()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
cout << q.front() << endl; //1
cout << q.back() << endl; //3
q.pop();
cout << q.front() << endl; //2
while(!q.empty())
{
cout << q.front() << " ";
q.pop();
}
return 0;
}
课后练习 OJ:用两个队列实现栈。
2.3 queue 模拟实现
底层选用std::list,list 支持高效头删 pop_front。
cpp
#include <list>
namespace bite
{
template<class T>
class queue
{
public:
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_front(); }
T& back() { return _c.back(); }
const T& back()const { return _c.back(); }
T& front() { return _c.front(); }
const T& front()const { return _c.front(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
std::list<T> _c;
};
}
3 priority_queue 优先级队列(堆)
3.1 概念
priority_queue优先级队列,本质就是堆(heap),容器适配器。
默认:大堆(大顶堆),堆顶 top () 永远是容器里面最大的元素 。 底层默认容器是
vector;内部调用堆算法make_heap、push_heap、pop_heap维护堆结构。
注意:
- pop () 删除的是堆顶最大 / 最小元素,不是最早入队的元素。
- 默认大堆;要小堆,需要传入比较仿函数
greater<T>。
3.2 priority_queue 接口
头文件#include <queue>,#include <vector>,小堆需要#include <functional>
表格
| 函数 | 说明 |
|---|---|
priority_queue<T> |
默认大堆 |
priority_queue<T,vector<T>,greater<T>> |
小堆 |
empty() |
判空 |
size() |
元素个数 |
top() |
获取堆顶元素(最大 / 最小) |
push(x) |
插入元素,自动调整堆结构 |
pop() |
删除堆顶元素 |
示例 1:内置类型,大堆、小堆
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
int main()
{
vector<int> v{3,2,7,6,0,4,1,9,8,5};
//默认大堆
priority_queue<int> q1;
for(auto e : v)
q1.push(e);
cout << q1.top() << endl; //9
//小堆,第三个模板参数 greater<int>
priority_queue<int,vector<int>,greater<int>> q2(v.begin(),v.end());
cout << q2.top() << endl; //0
return 0;
}
示例 2:自定义类型放入 priority_queue
大堆默认使用
<小于比较,自定义类需要重载operator<; 小堆greater需要重载operator>。
cpp
#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
:_year(year),_month(month),_day(day)
{}
//大堆需要重载 <
bool operator<(const Date& d)const
{
return (_year < d._year)
|| (_year == d._year && _month < d._month)
|| (_year == d._year && _month == d._month && _day < d._day);
}
//小堆greater需要重载 >
bool operator>(const Date& d)const
{
return (_year > d._year)
|| (_year == d._year && _month > d._month)
|| (_year == d._year && _month == d._month && _day > d._day);
}
friend ostream& operator<<(ostream& cout, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& cout, const Date& d)
{
cout << d._year << "-" << d._month << "-" << d._day;
return cout;
}
int main()
{
//大堆
priority_queue<Date> q1;
q1.push(Date(2018,10,29));
q1.push(Date(2018,10,28));
q1.push(Date(2018,10,30));
cout << q1.top() << endl; //2018‑10‑30
//小堆
priority_queue<Date,vector<Date>,greater<Date>> q2;
q2.push(Date(2018,10,29));
q2.push(Date(2018,10,28));
q2.push(Date(2018,10,30));
cout << q2.top() << endl; //2018‑10‑28
return 0;
}
OJ 例题:数组第 K 个最大元素
思路:把全部元素放进大堆,弹出 k‑1 次,堆顶就是第 K 大。
cpp
#include <queue>
#include <vector>
using namespace std;
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int> p(nums.begin(), nums.end());
for(int i = 0; i < k-1; ++i)
{
p.pop();
}
return p.top();
}
};
优化思路:可以维护大小为 k 的小堆,适合海量数据,不用全部加载内存。
3.3 priority_queue 模拟实现
底层基于堆算法,封装 vector,调用make_heap/push_heap/pop_heap,适配器模式。
4 容器适配器
4.1 适配器模式设计思想
适配器模式:把已有类的接口,包装转换成客户需要的另一套接口。 生活例子:不同国家电源插座,使用适配器转换插头接口。

stack、queue、priority_queue不是真正容器!是容器适配器。 本身不存放数据,包装其他底层容器,对外提供一套限制后的接口。
模板原型:
cpp
template <class T, class Container = deque<T>>
class stack;
template <class T, class Container = deque<T>>
class queue;
stack、queue默认底层容器是 deque 双端队列。
4.2 deque 双端队列底层原理(了解)
deque( 双端队列 ) :是一种双开口的 " 连续 " 空间的数据结构 ,双开口的含义是:可以在头尾两端
进行插入和删除操作,且时间复杂度为 O(1) ,与 vector 比较,头插效率高,不需要搬移元素;与
list 比较,空间利用率比较高。

deque 并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际 deque 类似于一个
动态的二维数组 ,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其 " 整体连续 " 以及随机访问
的假象,落在了 deque 的迭代器身上, 因此 deque 的迭代器设计就比较复杂,如下图所示:

那 deque 是如何借助其迭代器维护其假想连续的结构呢?

特性:
- 头插、尾插 O (1) 时间复杂度,不需要大量搬移元素,对比 vector 头插效率极高。
- 扩容只需要新增 buffer,不需要拷贝整块大数据。
- 缺点:迭代器复杂,遍历效率差;每移动迭代器都要判断是否跨 buffer 边界,不适合大量遍历场景。
deque 很少直接业务使用;最大用处就是作为 stack、queue 的底层适配器容器。
4.3 为什么 stack 和 queue 默认选择 deque 作为底层?
- stack、queue不需要迭代器,不需要遍历,刚好避开 deque 遍历慢的致命缺点。
- stack:只需要尾插尾删;deque 扩容不需要搬移大量数据,比 vector 扩容开销小。
- queue:需要尾插 push_back、头删 pop_front;vector 没有高效头删;list 内存碎片多;deque 兼顾效率和内存开销。
4.4 stack、queue 模板模拟完整版本(支持自定义底层容器)
cpp
#include<deque>
namespace bite
{
//stack适配器
template<class T, class Con = deque<T>>
class stack
{
public:
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_back(); }
T& top() { return _c.back(); }
const T& top()const { return _c.back(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
Con _c;
};
//queue适配器
template<class T, class Con = deque<T>>
class queue
{
public:
void push(const T& x) { _c.push_back(x); }
void pop() { _c.pop_front(); }
T& back() { return _c.back(); }
const T& back()const { return _c.back(); }
T& front() { return _c.front(); }
const T& front()const { return _c.front(); }
size_t size()const { return _c.size(); }
bool empty()const { return _c.empty(); }
private:
Con _c;
};
}
📔本篇核心考点总结
- stack 栈,后进先出 LIFO;push 入栈,pop 出栈(无返回),top 拿栈顶;OJ:最小栈、压入弹出序列、逆波兰表达式。
- queue 队列,先进先出 FIFO;push 队尾入,pop 队头出,front 队头,back 队尾。
- priority_queue 优先级队列,底层堆;默认大堆;小堆需要
greater<T>;自定义类型要重载比较运算符。 - stack、queue、priority_queue 属于容器适配器,不是原生容器,包装其他容器对外提供受限接口。
- 默认底层容器
deque双端队列;deque 头插尾插 O (1),扩容代价小,但是遍历性能差。 - stack 可以用 vector/list/deque;queue 底层不能用 vector,必须支持 pop_front。
面试简答 Q:stack 是容器吗?底层默认什么容器?
A:不是容器,是容器适配器;默认底层 deque。
Q:priority_queue 默认是大堆还是小堆?怎么改成小堆?
A:默认大堆;传入第三个模板参数
greater<T>实现小堆。Q:为什么 stack/queue 不用 vector 做默认底层?
A:queue 需要头删 pop_front,vector 头删效率 O (N);stack 虽然 vector 可以用,但 deque 扩容不需要拷贝大量元素,效率更好。
Q:逆波兰计算的时候 pop 顺序?A:先 pop 得到右操作数,再 pop 左操作数,顺序不能颠倒。

