【数据结构】栈和队列

一、基本概念

栈和队列都是操作受限的线性表 ,只允许在固定的一端进行插入和删除操作,核心区别完全来自存取规则

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) 效率,淘汰无用元素,是暴力算法的最优优化方案。
相关推荐
贾斯汀玛尔斯2 小时前
每天学一个算法--图算法(Graph Algorithms)
数据结构·算法
网安INF2 小时前
数据结构第四章复习:树与二叉树
数据结构
我是无敌小恐龙2 小时前
Java SE 零基础入门 Day02 运算符与流程控制超详细笔记
java·数据结构·spring boot·笔记·python·spring·spring cloud
念越3 小时前
算法每日一题 Day04|快慢双指针法解决环形链表问题
数据结构·算法·链表
求学的小高3 小时前
数据结构Day6(普通树、森林与二叉树的关系、哈夫曼编码、并查集)
数据结构·笔记·考研
算法鑫探3 小时前
贪心算法(C 语言实现)及经典应用
c语言·数据结构·算法·贪心算法
Peregrine94 小时前
数据结构 - > 双链表
c语言·数据结构·算法
qeen874 小时前
【数据结构】队列及其C语言模拟实现
c语言·数据结构·c++·学习·队列
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题】【Java基础篇】第4题:LinkedList是单向链表还是双向链表
java·开发语言·数据结构·后端·链表·面试·list