
🎈主页传送门:良木生香
🔥个人专栏:《C语言》《数据结构-初阶》《鼠鼠的C++学习之路》《Linux系统编程》
🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离
一、前言
在 C++ STL 中,
stack和queue是非常常用的容器。但很多人只停留在"会用"的层面,对其底层实现、设计思想以及经典应用场景并不熟悉。那么今天就从容器适配器 的设计思想出发,深入讲解stack和queue的底层原理,并配合经典面试题,帮助大家彻底掌握这部分知识。
二、Stack 与 Queue 的本质:容器适配器
2.1 什么是容器适配器?
适配器(Adapter) 是一种设计模式:将一个类的接口转换成用户期望的另一个接口。
stack 和 queue 本质上不是独立的容器,而是在已有容器基础上封装的适配器。它们不自己管理数据,而是复用底层容器的接口,只暴露栈/队列特有的操作。
cpp
// stack 的声明:底层默认用 deque
template <class T, class Container = deque<T>>
class stack;
// queue 的声明:底层默认也用 deque
template <class T, class Container = deque<T>>
class queue;
2.2 为什么叫"适配器"?
底层容器(Vector/List/Deque)
┌─────────────────────────────┐
│ push_back() pop_back() │
│ push_front() pop_front()│
│ insert() erase() │
│ operator[] front() │
│ ... │
└──────────┬──────────────────┘
│ 适配(限制接口)
↓
栈接口(只暴露这几个)
┌─────────────┐
│ push() │ ← 底层调用 push_back
│ pop() │ ← 底层调用 pop_back
│ top() │ ← 底层调用 back
│ size() │
│ empty() │
└─────────────┘
2.3 底层容器的选择
| 容器适配器 | 默认底层 | 可选底层 | 说明 |
|---|---|---|---|
stack |
deque<T> |
vector<T>、list<T> |
只需要尾插尾删 |
queue |
deque<T> |
list<T> |
需要头删尾插,不能用 vector (没有 pop_front) |
⚠️ 队列不能用 vector 的
pop_front(),因为 vector 头删需要 O(n) 移动所有元素。
三、Stack:后进先出(LIFO)
3.1 核心特性
push(5)
↓
┌─────────┐
│ 5 │ ← 栈顶(最后入栈,最先出栈)
├─────────┤
│ 4 │
├─────────┤
│ 3 │
├─────────┤
│ 2 │
├─────────┤
│ 1 │ ← 栈底(最先入栈,最后出栈)
└─────────┘
3.2 模拟实现(基于 Vector)
cpp
template<typename T, typename Container = vector<T>>
class Stack {
public:
void push(const T& x) { _con.push_back(x); }
void pop() { _con.pop_back(); }
T& top() { return _con.back(); }
size_t size() const { return _con.size(); }
bool empty() const { return _con.empty(); }
private:
Container _con;
};
3.3 时间复杂度
| 操作 | 复杂度 | 说明 |
|---|---|---|
push |
O(1) | 尾插 |
pop |
O(1) | 尾删 |
top |
O(1) | 访问末尾元素 |
四、Queue:先进先出(FIFO)
4.1 核心特性
出队(pop) 入队(push)
↓ ↓
┌───────┬───────┬───────┬───────┐
│ 1 │ 2 │ 3 │ 4 │
└───────┴───────┴───────┴───────┘
↑ ↑
front back
入队顺序:1 → 2 → 3 → 4
出队顺序:1 → 2 → 3 → 4(完全相同)
4.2 模拟实现(基于 List)
cpp
template<typename T, typename Container = list<T>>
class Queue {
public:
void push(const T& x) { _con.push_back(x); } // 队尾入
void pop() { _con.pop_front(); } // 队头出
T& front() { return _con.front(); }
T& back() { return _con.back(); }
size_t size() const { return _con.size(); }
bool empty() const { return _con.empty(); }
private:
Container _con;
};
4.3 循环队列(数组实现)
cpp
template<typename T, size_t N>
class CircularQueue {
T _data[N];
size_t _front = 0, _rear = 0, _size = 0;
public:
bool push(const T& x) {
if (_size == N) return false;
_data[_rear] = x;
_rear = (_rear + 1) % N;
++_size;
return true;
}
bool pop() {
if (_size == 0) return false;
_front = (_front + 1) % N;
--_size;
return true;
}
T& front() { return _data[_front]; }
bool empty() const { return _size == 0; }
bool full() const { return _size == N; }
};
五、List 迭代器的 operator-> 重载
5.1 问题场景
当 List 中存储的是结构体时,如何通过迭代器访问成员?
cpp
struct Point {
int _x, _y;
};
List<Point> points;
points.push_back({1, 2});
auto it = points.begin();
// (*it)._x = 10; // 方式1:先解引用再访问成员
// it->_x = 10; // 方式2:更简洁,需要 operator-> 支持
6.2 operator-> 的实现
cpp
template<typename T, typename Ref, typename Ptr>
struct __ListIterator {
Node* _node;
// 解引用,返回 data 的引用
Ref operator*() { return _node->_data; }
// 返回 data 的指针
Ptr operator->() { return &_node->_data; }
};
// it->x 的调用过程:
// it.operator->() → &_node->_data → (&_node->_data)->x
// 编译器会自动优化为直接访问成员
六、经典面试题详解
6.1 最小栈(O(1) 获取最小值)
思路:用两个栈,一个存数据,一个存最小值。
cpp
class MinStack {
stack<int> _data; // 数据栈
stack<int> _min; // 辅助栈:存当前最小值
public:
void push(int x) {
_data.push(x);
// 如果 x 比之前的最小值还小(或等于),压入 x
if (_min.empty() || x <= _min.top()) {
_min.push(x);
} else {
_min.push(_min.top()); // 重复压入当前最小值
}
}
void pop() {
_data.pop();
_min.pop();
}
int top() { return _data.top(); }
int getMin() { return _min.top(); }
};
// push 5: _data=[5], _min=[5]
// push 2: _data=[5,2], _min=[5,2]
// push 7: _data=[5,2,7], _min=[5,2,2]
// push 1: _data=[5,2,7,1], _min=[5,2,2,1]
// getMin() → 1
// pop() → _data=[5,2,7], _min=[5,2,2]
// getMin() → 2
⚠️ 注意 :当
data > min.top()时,minst不 push。但相同 min 时两边同时进,否则删除后minst不更新!
6.2 栈的压入/弹出序列验证
思路:用辅助栈模拟入栈过程,同时比较出栈序列。
cpp
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> st;
int j = 0; // 指向 popped 的索引
for (int x : pushed) {
st.push(x); // 入栈序列入栈
// 栈顶与出栈序列比较
while (!st.empty() && st.top() == popped[j]) {
st.pop();
++j; // 匹配成功,继续比较下一个
}
}
// 当入栈序列走完,但出栈序列不为空,则为 false
return st.empty();
}
6.3 逆波兰表达式求解
中缀转后缀示例:
中缀:1 + 2 × (3 + 4) / 5
后缀:1 2 3 4 + × 5 / +
计算过程:
遇到 1:压栈 [1]
遇到 2:压栈 [1, 2]
遇到 3:压栈 [1, 2, 3]
遇到 4:压栈 [1, 2, 3, 4]
遇到 +:弹出 4 和 3,计算 3+4=7,压栈 [1, 2, 7]
遇到 ×:弹出 7 和 2,计算 2×7=14,压栈 [1, 14]
遇到 5:压栈 [1, 14, 5]
遇到 /:弹出 5 和 14,计算 14/5=2,压栈 [1, 2]
遇到 +:弹出 2 和 1,计算 1+2=3,压栈 [3]
结果:3
cpp
int evalRPN(vector<string>& tokens) {
stack<int> st;
for (const string& token : tokens) {
if (token == "+" || token == "-" || token == "*" || token == "/") {
int b = st.top(); st.pop();
int a = st.top(); st.pop();
if (token == "+") st.push(a + b);
else if (token == "-") st.push(a - b);
else if (token == "*") st.push(a * b);
else if (token == "/") st.push(a / b);
} else {
st.push(stoi(token));
}
}
return st.top();
}
6.4 用两个栈实现队列
cpp
class MyQueue {
stack<int> _in; // 入队栈
stack<int> _out; // 出队栈
public:
void push(int x) { _in.push(x); }
int pop() {
peek();
int val = _out.top();
_out.pop();
return val;
}
int peek() {
if (_out.empty()) {
while (!_in.empty()) {
_out.push(_in.top());
_in.pop();
}
}
return _out.top();
}
bool empty() { return _in.empty() && _out.empty(); }
};
6.5 二叉树的层序遍历(队列应用)
cpp
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
if (!root) return result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int levelSize = q.size();
vector<int> level;
for (int i = 0; i < levelSize; ++i) {
TreeNode* node = q.front();
q.pop();
level.push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
result.push_back(level);
}
return result;
}
七、总结
| 容器适配器 | 核心特性 | 底层结构 | 典型应用 |
|---|---|---|---|
| stack | LIFO | deque/vector/list | 括号匹配、DFS、函数调用、撤销 |
| queue | FIFO | deque/list | BFS、消息队列、任务调度 |
关键要点回顾
- 容器适配器 :
stack和queue不是独立容器,而是复用底层容器的适配器 - 队列不能用 vector :因为 vector 没有高效的
pop_front() - 默认底层 :
stack和queue默认用deque - 最小栈:双栈实现,辅助栈同步压入当前最小值
- 逆波兰表达式:遇到数字入栈,遇到运算符弹出两个数计算
- 栈序列验证:辅助栈模拟入栈,同时比较出栈序列
那么以上就是本次所有的内容了
文章是自己写的哈,有什么描述不对的、不恰当的地方,恳请大佬指正,看到后会第一时间修改,感谢您的阅读~~~~