【数据结构】线性表—栈与队列

什么是栈和队列

栈(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();        //返回栈的大小

 

posted @   綾川雪絵  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示