C++ STL 栈(stack)与队列(queue)容器详解
📚 思维导图概览
一、栈(stack)容器
1.1 基本概念
栈(Stack)是一种先进后出(FILO: First In Last Out)的线性数据结构,操作特性类似于现实中的子弹夹或叠盘子。其核心操作限制为:
- 只能在容器的一端(栈顶)进行插入和删除操作
- 不提供遍历功能,只能访问栈顶元素
- 典型应用场景:函数调用栈、表达式求值、括号匹配等
示意图:
1.2 底层实现探秘
stack作为容器适配器,默认使用deque作为底层容器,但我们也可以自由选择:
底层容器 | 特性对比 | 适用场景 |
---|---|---|
deque | 默认选择,头尾操作高效 | 通用场景 |
vector | 动态数组,尾部操作O(1) | 需要连续内存空间时 |
list | 双向链表,任意位置操作O(1) | 频繁插入删除中间元素时 |
底层代码如下:
// 自定义底层容器的实现方法
stack<int, vector<int>> vecStack; // 使用vector作为底层
stack<string, list<string>> listStack; // 使用list作为底层
二、接口全解与性能分析
2.1 核心接口性能表
操作 | 时间复杂度 | 注意事项 |
---|---|---|
push() | O(1) | 可能触发容器扩容 |
pop() | O(1) | 空栈调用导致未定义行为 |
top() | O(1) | 返回引用,可直接修改栈顶元素 |
empty() | O(1) | 建议先检查非空再操作 |
size() | O(1) | 返回值为无符号类型 |
2.2 相关接口关键字如下:
构造函数:
- stack<T> stk; //stack采用模板类实现, stack对象的默认构造形式
- stack(const stack &stk); //拷贝构造函数
赋值操作:
- stack& operator=(const stack &stk); //重载等号操作符
数据存取:
- push(elem); //向栈顶添加元素
- pop(); //从栈顶移除第一个元素
- top(); //返回栈顶元素
大小操作:
- empty(); //判断堆栈是否为空
- size(); //返回栈的大小
2.3 相关接口案例代码如下:
#include<iostream>
using namespace std;
#include<stack>
void test01()
{
// 特点:先进后出数据结构
stack<int>s;
// 入栈
s.push(10);
s.push(20);
s.push(30);
s.push(40);
cout<<"栈的大小为:"<<s.size()<<endl;
// 只要栈不为空,查看栈顶,并且执行出栈操作
while (!s.empty())
{
// 查看栈顶元素
cout<<"栈顶元素为:"<<s.top()<<endl;
// 出栈
s.pop();
}
cout<<"栈的大小为:"<<s.size()<<endl;
}
int main()
{
test01();
system("pause");
return 0;
}
2.4 经典错误案例
stack<int> s;
s.pop(); // 错误!空栈弹出导致崩溃
s.top() = 5; // 正确,可以直接修改栈顶值
// 安全操作示范
if (!s.empty()) {
int val = s.top();
s.pop();
}
1.4 应用场景
- 撤销操作(Undo)机制
- 递归函数调用
- 浏览器的前进后退功能
- 迷宫问题路径记录
二、队列(queue)容器
2.1 基本概念
队列(Queue)是一种先进先出(FIFO: First In First Out)的线性数据结构,操作特性类似于现实中的排队场景:
- 允许在队尾添加元素(入队)
- 允许在队头移除元素(出队)
- 典型应用场景:消息队列、打印机任务队列、BFS算法
示意图:
2.2 常用接口
构造函数
queue<T> que; // 默认构造
queue<T, list<T>> que; // 指定list为底层容器
queue(const queue& que); // 拷贝构造
数据操作
方法 | 功能描述 | 时间复杂度 |
---|---|---|
push(elem) | 元素入队 | O(1) |
pop() | 队头元素出队 | O(1) |
front() | 返回队头元素 | O(1) |
back() | 返回队尾元素 | O(1) |
容量操作
方法 | 功能描述 | 时间复杂度 |
---|---|---|
empty() | 判断队列是否为空 | O(1) |
size() | 返回队列元素个数 | O(1) |
2.3相关接口案例代码如下:
#include<iostream>
using namespace std;
#include<queue>
#include<string>
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void test01()
{
// 特点:先进先出数据结构
// 创建队列
queue<Person>q;
// 准备数据
Person p1("迪迦", 100);
Person p2("泰罗", 150);
Person p3("戴拿", 50);
Person p4("雷欧", 200);
// 入队
q.push(p1);
q.push(p2);
q.push(p3);
q.push(p4);
cout<<"队列大小为:"<<q.size()<<endl;
// 判断只要队列不为空,查看队头和队尾,出队
while (!q.empty())
{
// 查看队头
cout<<"队头元素————姓名:"<<q.front().m_Name<<"年龄:"<<q.front().m_Age<<endl;
// 查看队尾
cout<<"队尾元素————姓名:"<<q.back().m_Name<<"年龄:"<<q.back().m_Age<<endl;
// 出队
q.pop();
}
cout<<"队列大小为:"<<q.size()<<endl;
}
int main()
{
test01();
system("pause");
return 0;
}
2.4 应用场景
- 多线程任务调度
- 网络数据包缓冲
- 广度优先搜索(BFS)
- 订单处理系统
三、对比与选择
特性 | stack | queue |
---|---|---|
数据结构 | FILO(先进后出) | FIFO(先进先出) |
操作端 | 单端操作(栈顶) | 双端操作(队头/队尾) |
典型应用 | 撤销机制、递归处理 | 任务调度、消息缓冲 |
底层实现 | 默认deque,可配vector/list | 默认deque,可配list |
遍历方式 | 需要转移元素 | 需要转移元素 |
四、注意事项
- 容器适配器:stack/queue是基于其他容器实现的适配器
- 元素访问限制:
- stack只能访问top元素
- queue只能访问front和back元素
- 线程安全:STL容器非线程安全,需自行加锁
- 异常处理:empty检查后再进行top/front/pop操作
五、常见面试题
Q1:用两个栈实现队列?
思路:使用两个栈,一个负责入队(in
),一个负责出队(out
)。当需要出队时,若 out
栈为空,则将 in
栈中的元素全部倒入 out
栈。
#include <stack>
using namespace std;
class MyQueue {
private:
stack<int> in;
stack<int> out;
void transfer() {
while (!in.empty()) {
out.push(in.top());
in.pop();
}
}
public:
MyQueue() {}
void push(int x) {
in.push(x);
}
int pop() {
if (out.empty()) transfer();
int val = out.top();
out.pop();
return val;
}
int peek() {
if (out.empty()) transfer();
return out.top();
}
bool empty() {
return in.empty() && out.empty();
}
};
Q2:用队列实现栈?
思路:使用单个队列,每次插入新元素后,将前面的元素依次出队再入队,使得新元素位于队首。
#include <queue>
using namespace std;
class MyStack {
private:
queue<int> q;
public:
MyStack() {}
void push(int x) {
q.push(x);
for (int i = 0; i < q.size() - 1; ++i) {
q.push(q.front());
q.pop();
}
}
int pop() {
int val = q.front();
q.pop();
return val;
}
int top() {
return q.front();
}
bool empty() {
return q.empty();
}
};
Q3:最小栈设计(O(1)时间获取最小值)?
思路:使用辅助栈同步记录当前最小值。
#include <stack>
using namespace std;
class MinStack {
private:
stack<int> s;
stack<int> min_s;
public:
MinStack() {}
void push(int val) {
s.push(val);
if (min_s.empty() || val <= min_s.top()) {
min_s.push(val);
}
}
void pop() {
if (s.top() == min_s.top()) {
min_s.pop();
}
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return min_s.top();
}
};
Q4:循环队列实现?
思路:使用数组存储数据,维护头尾指针 front
和 rear
,并通过 size
变量简化边界判断。
class MyCircularQueue {
private:
vector<int> data;
int front;
int rear;
int size;
int capacity;
public:
MyCircularQueue(int k) {
data.resize(k);
front = 0;
rear = 0;
size = 0;
capacity = k;
}
bool enQueue(int value) {
if (isFull()) return false;
data[rear] = value;
rear = (rear + 1) % capacity;
size++;
return true;
}
bool deQueue() {
if (isEmpty()) return false;
front = (front + 1) % capacity;
size--;
return true;
}
int Front() {
return isEmpty() ? -1 : data[front];
}
int Rear() {
return isEmpty() ? -1 : data[(rear - 1 + capacity) % capacity];
}
bool isEmpty() {
return size == 0;
}
bool isFull() {
return size == capacity;
}
};
Q5:栈的压入、弹出序列合法性判断?
思路:模拟压栈过程,遍历弹出序列,若当前栈顶不是目标元素,则按压入序列继续压栈。
#include <stack>
#include <vector>
using namespace std;
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> s;
int i = 0;
for (int num : pushed) {
s.push(num);
while (!s.empty() && s.top() == popped[i]) {
s.pop();
i++;
}
}
return s.empty();
}