一、基本概念
栈和队列都是操作受限的线性表 ,只允许在固定的一端进行插入和删除操作,核心区别完全来自存取规则。
1. 栈(Stack):后进先出 LIFO
栈就像叠放的盘子、装子弹的弹夹,只能从最顶端放入元素、取出元素。
- 核心规则:后进先出(Last In First Out),最后放入的元素,最先被取出
- 唯一操作端:栈顶(top),所有入栈、出栈、查栈顶元素,都只能操作栈顶
- 禁止操作:不允许在栈中间、栈底插入 / 删除元素
2. 队列(Queue):先进先出 FIFO
队列就像排队买票、流水线作业,从队尾进入队伍,从队头离开队伍。
- 核心规则:先进先出(First In First Out),最先放入的元素,最先被取出
- 双操作端:队尾(rear)负责入队,队头(front)负责出队
- 禁止操作:不允许在队列中间插入 / 删除元素
二、数组模拟栈
数组模拟栈的思路非常简单:用一个数组 stk[] 存储数据,用一个变量 tt 标记当前栈顶的下标,所有操作只操作 tt 的位置。
1.核心规则
- 入栈:tt++,再赋值存入元素
- 出栈 :取 tt 位置的值,再
tt-- - 判空 :
tt != 0即为空栈
2.例题:模拟栈
题目描述(来源于AcWing)
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int m;
int stk[N],tt;
//队头插入元素
void add(int x){
tt++;
stk[tt] = x;
}
//弹出队尾元素
void del(){
tt--;
}
//判断是否为空
void emp(){
if(tt == 0){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
//查找队头元素
void q(){
cout<< stk[tt] <<endl;
}
int main(){
cin>>m;
while(m--){
string s;
cin>>s;
if(s == "push"){
int x;
cin>>x;
add(x);
}else if(s == "pop"){
del();
}else if(s == "empty"){
emp();
}else if(s == "query"){
q();
}
}
return 0;
}
三、数组模拟队列
核心思路:用数组 q[] 存储数据,用 head 标记队头下标,tt 标记队尾。
1.核心规则
- 入队:赋值给 tt 位置,
tt++ - 出队:取 head 位置的值,
head++ - 判空:
head <= tt
2.例题(来源于AcWing)
题目描述
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int m;
int q[N], head,tt = -1; //队头队尾
//队尾插入元素
void add(int x){
tt++;
q[tt] = x;
}
//弹出队头元素
void del(){
head++;
}
//判断是否为空
void emp(){
if(head > tt){
cout<<"YES"<<endl;
}else{
cout<<"NO"<<endl;
}
}
//查找队头元素
void qry(){
cout<< q[head] <<endl;
}
int main(){
cin>>m;
while(m--){
string s;
cin>>s;
if(s == "push"){
int x;
cin>>x;
add(x);
}else if(s == "pop"){
del();
}else if(s == "empty"){
emp();
}else if(s == "query"){
qry();
}
}
return 0;
}
四、单调栈
1. 什么是单调栈?
单调栈本质是栈内元素始终保持严格单调递增 / 单调递减顺序的特殊栈,入栈时会破坏单调性的元素,会先弹出栈,再将当前元素入栈。
- 单调递增栈:栈底到栈顶,元素严格递增
- 单调递减栈:栈底到栈顶,元素严格递减
2. 单调栈的核心作用
只需要一次遍历 O (n) 时间复杂度,就能找到数组中每个元素「左边 / 右边第一个比它大 / 小的元素」,暴力解法需要 O (n²),单调栈是最优解。
3. 例题:寻找每个元素左侧第一个比它小的元素
题目描述(来源于AcWing)
思路
- 如果栈不为空 ,并且栈顶元素 >= 当前数 x ,说明这个栈顶元素绝对不可能成为 x 或后面任何数的答案 ,直接弹出(
tt--)直到栈空 或 栈顶 < x 为止 - 经过上面的循环,栈顶就是左边第一个比 x 小的数,如果栈空 → 输出 -1,否则 → 输出栈顶。
- 当前数 x 可能成为后面数字的答案,把 x 压入栈,继续保持递增栈结构。
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
int stk[N],tt;
//队头插入元素
void add(int x){
tt++;
stk[tt] = x;
}
//弹出队尾元素
void del(){
tt--;
}
int main(){
cin>>n;
for(int i = 0; i < n; i++){
int x;
cin>>x;
//如果栈非空,且栈内元素 >= x,则这个元素一定不是答案,所以弹出这个元素
while(tt && stk[tt] >= x){
tt--;
}
if(tt){
//非空---比他小的
cout<<stk[tt]<<" ";
}else{
//空
cout<< -1 << " ";
}
//存储
tt++;
stk[tt] = x;
}
return 0;
}
五、单调队列
1. 什么是单调队列?
单调队列是队列内元素始终保持严格单调递增 / 单调递减顺序的特殊队列,入队时,从队尾开始弹出所有破坏单调性的元素,再将当前元素入队,始终维护队列单调性。
2. 单调队列的核心作用
O (n) 时间复杂度,解决滑动窗口内的最大值、最小值问题,暴力解法需要 O (nk),是滑动窗口极值问题的唯一最优解。
3.例题:寻找滑动窗口最大值、最小值
题目描述(来源于AcWing)
思路
- 用双端队列 存数组下标,维护队列单调
- 遍历每个元素,先踢掉超出窗口的队头元素
- 再踢掉不可能成为最值的队尾元素
- 新元素入队,窗口形成后输出队头
- 最小值用递增队列 ,最大值用递减队列
代码
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N];//输入元素
int q[N];//队列--存储下标
int n;//元素个数
int k;//滑动窗口长度
int main(){
cin>>n>>k;
for(int i = 0; i < n; i++){
cin>>a[i];
}
//找最小
int head = 0;//队头
int tt = -1;//队尾
for(int i = 0; i < n; i++){
//判断队头是否已经超出窗口
if(head <= tt && i - k + 1 > q[head]){
head++;
}
//如果新插入的元素 < 队列内的元素,则将这个队列里的元素弹出
while(head <= tt && a[q[tt]] >= a[i]){
tt--;
}
//把当前这个元素的下标存储起来
tt++;
q[tt] = i;
//输出
if(i >= k - 1 ){
cout<<a[q[head]] <<" ";
}
}
cout<<endl;
//找最大
head = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
//判断队头是否已经超出窗口
if (head <= tt && i - k + 1 > q[head]){
head++ ;
}
//如果新插入的元素 > 队列内的元素,则将这个队列里的元素弹出
while (head <= tt && a[q[tt]] <= a[i]){
tt -- ;
}
//把当前这个元素的下标存储起来
tt++;
q[tt] = i;
if (i >= k - 1){
cout<< a[q[head]] <<" ";
}
}
return 0;
}
六、总结
- 栈(后进先出)、队列(先进先出),数组模拟靠指针控制边界,循环队列最优;
- 单调栈 = 栈 + 单调性,解决单侧第一个最值 问题;单调队列 = 队列 + 单调性,解决滑动窗口最值问题;
- 二者核心都是O (n) 效率,淘汰无用元素,是暴力算法的最优优化方案。




