【数据结构】线性表—栈与队列
什么是栈和队列
栈(stack),是一种"后进先出"(Last In First Out,LIFO)的线性表,其限制是只允许在表的一端进行插入和删除运算。比如往桌子上放盘子,往上放盘子(压栈)后,只能从最上面(栈顶)取盘子(弹栈)。
队列(queue),是一种"先进先出" (First in First Out,FIFO)的线性表,其限制是允许在表达的一端进行删除操作,另一端进行插入运算。
栈和队列的数组模拟实现以及常见基本算法
栈的实现:
int stack[MAXN]; //开辟栈需要的数组空间
int top = 0; //栈顶指针,指向下一个待插入的数组位置
结构体方法:
#define MAXSIZE 50 //定义栈中元素的最大个数
typedef int ElemType; //ElemType的类型根据实际情况而定,这里假定为int
typedef struct{
ElemType data[MAXSIZE];
int top; //用于栈顶指针
}SqStack;
基本算法:
判断栈是否为空:
bool stackEmpty(int top) {
if (top == 0) return true;
else
return false;
}
判断栈是否满栈:
bool stackOverflow (int top) {
if (top >= MAXN) return true;
else
return false;
}
压栈操作:
void push(int x) {
if(top >= MAXN)
cout << "Stack is overflow";
else
stack[top++]=x; //stack[top]=x;top++;
}
出栈操作:
void pop() {
if (top == 0)
cout << "Stack is empty.";
else
p--;
}
查找栈顶元素:
int topValue() {
if (top == 0) {
cout << "Stack is empty.";
return -1;
}
else
return stack[top-1]; //top是下一个待插入栈空间的指针,所以栈顶是top-1;
}
队列的实现:
int queue[MAXN]; //开辟队列需要的数组空间
int head = 0; //队首指针
int tail = 0; //队尾指针,如果有新的元素插入,就会插入到这个位置
结构体方法:
#define MAXSIZE 50 //定义队列中元素的最大个数
typedef struct{
<ElemType> data[MAXSIZE]; //存放队列元素
int front,rear;
}SqQueue;
基本算法:
判断队列是否为空:
bool isEmpty() {
if(head == tail)
return true;
else
return false;
}
判断队列是否溢出:
bool isOverflow() {
if (tail >= MAXN)
return true;
else
return false;
}
进队:
void push(int x) {
if (tail >= MAXN) //判断队列是否溢出
printf("Queue is overflow");
else
queue[tail++] = x; //queue[tail]=x;tail++;
}
出队:
void pop() {
if(head == tail)
printf("Queue is empty.");
else
head++;
}
查询队首队元素:
int frontValue() {
if(head == tail) {
printf("Queue is empty");
return -1;
}
else
return queue[head];
}
单调栈和单调队列
什么是单调栈?
从名字中就可以听出来,单调栈中存放的数据是有序的,所以单调栈也分为单调递增栈和单调递减栈
- 单调递增栈:单调递增栈就是从栈底到栈顶数据是从小到大
- 单调递减栈:单调递减栈就是从栈底到栈顶数据是从大到小
模拟单调栈数据的push和pop:
现在有一组数字{10,3,7,4,12},实现一个递减单调栈。从左到右依次入栈,则如果栈为空或者入栈元素小于栈顶元素值,则入栈;否则,如果入栈元素会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。单调递减栈反之。
- 10入栈时,栈为空,直接入栈;栈内元素{10}
- 3入栈时,栈顶元素10比3大,直接入栈;栈内元素{10,3}
- 7入栈时,栈顶元素3比7小,栈顶元素出栈,之后栈顶元素为10,比7大,7入栈;栈内元素{10,7}
- 4入栈时,栈顶元素7比4大,4入栈;栈内元素{10,7,4}
- 12入栈时,栈顶元素一直比12小,一直出栈直到栈为空,此后12入栈;栈内元素{12}
单调栈的代码和应用:
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
样例:
input:
5 3 4 2 7 5
output:
-1 3 -1 2 2
思路:用单调递增栈,当该元素可以入栈的时候,栈顶元素就是它左侧第一个比它小的元素。
代码如下:
#include <iostream>
using namespace std;
const int N = 100010;
int stack[N], top;
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
scanf("%d", &x);
while (top && stack[top] >= x) top -- ;//如果栈顶元素大于当前待入栈元素,则出栈
if (!top) printf("-1 ");//如果栈空,则没有比该元素小的值。
else printf("%d ", stack[top]);//栈顶元素就是左侧第一个比它小的元素。
stack[ ++ top] = x;
}
return 0;
}
图解如下:
此题还有诸多变形:
什么是单调队列?
“如果一个选手比你小还比你强,你就可以退役了。” --单调队列的原理
- 【单调】指的是元素的规律 -- 递增或者递减
- 【队列】指的是元素只能从队头到队尾进行操作
单调队列主要是一种用于解决滑动窗口类问题的数据结构,即在一个长度为n的序列中,求每个长度为m的区间的区间最值,单调队列的时间复杂度是O(n),在这个问题中比O(nlogn)的ST表和线段树更优越。
暴力的做法很简单,对于每个i~i+m-1的序列逐个比较找出最值,时间复杂度为O(m*n)。
min(a[j])/max(a[j]),i-m+1 <= j <= i;
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 3e9;
LL a[1000010];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i <= n - k; i++) {
LL mini = INF;
for (int j = i; j < i + k; j++) mini = min(mini, a[j]);
cout << mini << ' ';
}
cout << "\n";
for (int i = 0; i <= n - k; i++) {
LL maxi = -INF;
for (int j = i; j < i + k; j++) maxi = max(maxi, a[j]);
cout << maxi << ' ';
}
return 0;
}
要求的是每个连续的m个数中的最大(最小)值,很明显,当一个数要进入所要“寻找”的最大值的范围中时,若这个数比其前面(先进队的数)要大,显然,前面的数会比这个数先出队且不可能是最大值
也就是说--满足以上条件是,可将前面的数“弹出”,然后将该数push进队
这相当于维护了一个递减的队列,减少了重复比较的次数,不仅如此,由于维护出的队列是查询范围内且是单调递减的,队头必然是查询区间内的最大值,输出时只需要输出队头即可。
显而易见的是,这个算法中,每个数只需要进队和出队一次,因此时间复杂度为O(n)
并且我们发现:单调队列中队尾进队出队,队头出队(维护子序列的单调性)
下面是例题:
题目描述
给定一个大小为n≤1e6的数组。有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。
您只能在窗口中看到k个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为[1 3 -1 -3 5 3 6 7],k为3。
窗口位置 最小值 最大值
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7
您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。
思路:
我们用queue来表示单调队列,p来表示其所对应的在原列表里的序号。
由于此时队中没有一个元素,我们直接令1进队。此时,q={1},p={1}。
现在3面临着抉择。下面基于这样一个思想:假如把3放进去,如果后面2个数都比它大,那么3在其有生之年就有可能成为最小的。此时,q={1,3},p={1,2}
下面出现了-1。队尾元素3比-1大,那么意味着只要-1进队,那么3在其有生之年必定成为不了最小值,原因很明显:因为当下面3被框起来,那么-1也一定被框起来,所以3永远不能当最小值。所以,3从队尾出队。同理,1从队尾出队。最后-1进队,此时q={-1},p={3}
出现-3,同上面分析,-1>-3,-1从队尾出队,-3从队尾进队。q={-3},p={4}。
出现5,因为5>-3,同第二条分析,5在有生之年还是有希望的,所以5进队。此时,q={-3,5},p={4,5}
出现3。3先与队尾的5比较,3<5,按照第3条的分析,5从队尾出队。3再与-3比较,同第二条分析,3进队。此时,q={-3,3},p={4,6}
出现6。6与3比较,因为3<6,所以3不必出队。由于3以前元素都<3,所以不必再比较,6进队。因为-3此时已经在滑动窗口之外,所以-3从队首出队。此时,q={3,6},p={6,7}
出现7。队尾元素6小于7,7进队。此时,q={3,6,7},p={6,7,8}。
那么,我们对单调队列的基本操作已经分析完毕。因为单调队列中元素大小单调递*(增/减/自定义比较),
因此,队首元素必定是最值。按题意输出即可。
代码实现:
#include <iostream>
using namespace std;
const int N = 1000010;
int n,k,hh = 0,tt = -1;
int a[N],queue[N]; //queue存储下标,方便判断队头出队
int main () {
scanf ("%d%d",&n,&k);
for (int i = 0;i < n;i++) cin >> a[i];
//找子序列最小值
for (int i = 0;i < n;i++) {
if (hh <= tt && i-k+1 > q[hh]) hh++; //判断队头是否滑出了窗口,滑出则从队头出队 i-k+1是窗口的前端点
while (hh <= tt && a[q[tt]] >= a[i]) tt--; //队尾出队(队列不空且新元素更优,即新元素小于或者等于队尾元素)
q[++tt] = i; //队尾入队
if (i >= k-1) printf ("%d ",a[q[hh]]); //输出最值
}
puts ("");
hh = 0,tt = -1; //初始化
//找子序列最大值
for (int i = 0;i < n;i++) {
if (hh <= tt && i-k+1 > q[hh]) hh++;
while (hh <= tt && a[q[tt]] <= a[i]) tt--; //队尾出队(队列不空且新元素更优,即新元素大于或者等于队尾元素)
q[++tt] = i;
if (i >= k-1) printf ("%d ",a[q[hh]]);
}
return 0;
}
STL中的栈和队列
C++也提供了栈和队列这些数据结构STL,我们在使用时只需要导头文件即可
栈:
要使用stack,应先添加头文件<stack>
建立一个栈:stack<typename> stk;建立一个stk栈,数据类型为typename
常用函数:
(1). push(x) 将x入栈
(2). top() 获得栈顶元素
(3). pop() 用以弹出栈顶元素
(4). empty() 可以检测stack内是否为空,返回true为空,返回false为非空
(5). size() 返回stack内元素的个数
队列:
队列的头文件是<queue>
建立一个队列:queue<typename> que;建立一个队列que,内部元素类型为typename
常用函数:
1.push(value); //往队尾添加元素
2.pop(); //在队头删除第一个元素
3.back(); //返回第一个元素
4.front(); //返回最后一个元素
5.empty(); //判断栈是否为空
6.size(); //返回栈的大小
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!