栈和队列(双端、单调、优先队列)

STL分类

img

栈和队列基本概念

栈(Stack)和队列(Queue)都是常见的数据结构,用于存储和操作一组元素。它们在结构和操作方式上有所不同。

1.栈

  • 栈是一种线性数据结构,具有后进先出(LIFO)的特点。即最后入栈的元素最先被访问或移除。

  • 栈有两个基本操作:压栈(push)和弹栈(pop)。压栈将元素添加到栈顶,而弹栈则将栈顶的元素移除并返回。

  • 栈的插入和删除操作只能在栈顶进行,因此栈是 一个只能从一端访问的数据结构。

在这里插入图片描述

  • 可以用数据模拟栈,也可以用STL模板库

    #define N 1000//栈的最大容量
    int sta[N],top=0;
    void pop()
    {
    	if(top>0) top--;
    }
    void push(int x)
    {
    	if(top<n-1) sta[++top]=x;	
    }
    int getTop()
    {
        return sta[top];
    }
    bool isEmpty()
    {
    	return top==0;
    }
    

    STL模板库需要添加头文件#include<stack>

    stack<typename> name;
    其中,typename可以是任何基本类型,例如 intdoublechar、结构体等,也可以是STL标准容器,例如vectorsetqueue等。

  • 函数说明接口说明
    stack()构造空的栈
    s.empty()检测stack是否为空
    s.size()返回stack中元素的个数
    s.top()返回栈顶元素的引用
    s.push(x)将元素val压入stack中
    s.pop()将stack中尾部的元素弹出

2.单调栈

单调栈(Monotone Stack):一种特殊的栈。在栈的「先进后出」规则基础上,要求「从 栈顶栈底 的元素是单调递增(或者单调递减)」。其中满足从栈顶到栈底的元素是单调递增的栈,叫做「单调递增栈」。满足从栈顶到栈底的元素是单调递减的栈,叫做「单调递减栈」。

详见博客

2.1 寻找左侧第一个比当前元素大的元素
  • 从左到右遍历元素,构造单调递栈:
    • 一个元素左侧第一个比它大的元素就是将其「插入单调递增栈」时的栈顶元素。
    • 如果插入时的栈为空,则说明左侧不存在比当前元素大的元素。
2.2 寻找左侧第一个比当前元素小的元素
  • 从左到右遍历元素,构造单调递增栈:
    • 一个元素左侧第一个比它小的元素就是将其「插入单调递减栈」时的栈顶元素。
    • 如果插入时的栈为空,则说明左侧不存在比当前元素小的元素。
2.3 寻找右侧第一个比当前元素大的元素
  • 从左到右遍历元素,构造单调递
    • 一个元素右侧第一个比它大的元素就是将其「弹出单调递减栈」时即将插入的元素。
    • 如果该元素没有被弹出栈,则说明右侧不存在比当前元素大的元素。
2.4 寻找右侧第一个比当前元素小的元素
  • 从左到右遍历元素,构造单调递
    • 一个元素右侧第一个比它小的元素就是将其「弹出单调递增栈」时即将插入的元素。
    • 如果该元素没有被弹出栈,则说明右侧不存在比当前元素小的元素。

无论哪种题型,都建议从左到右遍历元素。
查找 「比当前元素大的元素」 就用 单调递减栈,查找 「比当前元素小的元素」 就用 单调递增栈
「左侧」 查找就看 「插入栈」 时的栈顶元素,从 「右侧」 查找就看 「弹出栈」 时即将插入的元素。

3.队列

只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有 - 先进先出。
入队列:进行插入操作的一端称为队尾 Tail(rear)
出队列:进行删除操作的一端称为队头 Head(front)

队列示意图

【空队列】:空队列中没有元素,此时,队头下标和队尾下标均为 0,即front = rear = 0

空队列

【非空非满队列】:队列不是空队列且有剩余空间:

非空非满队列

【满队列】:顺序队列分配的固定空间用尽,没有多余空间,不能再插入元素,此时 front = 0rear = MAXSIZE

满队列

从上图中可以看出,非空队列的队尾下标 rear 始终是队尾元素的下一个元素的下标。

可以用数据模拟栈,也可以用STL模板库

const int maxn=1e5;
int que[maxn],front=0,rear=0;
void push(int x)
{
	que[rear++]=x;
}
int pop()
{
	return que[front++];
}
int getFront()
{
	return que[front];
}
int getRear()
{
	return que[rear-1];
}
bool isEmpty()
{
	return front==rear;
}
bool isFull()
{
	return rear>=maxSize;
}

STL模板库需要添加头文件#include<queue>

queue<typename> name;
其中,typename可以是任何基本类型,例如 intdoublechar、结构体等,也可以是STL标准容器,例如vectorsetqueue等。

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

入队出队过程图

上图也出现了什么问题?

满队列的是空间用尽,不能再插入元素的队列,虽然图5的队列也不能继续插入元素了,但它还有剩余空间,所以这样的队列还不能称之为满队列,可称之为假满队列

怎么解决呢?报错是 rear 越界导致,而队列的前大部分都是空闲的,所以当 rear 越界时,我们可不可以将其移动到下标 0 处呢?

img

显然是可以的,这样就构成了一个“循环”,我们称这种 frontrear可以循环利用的队列为循环队列

4.循环队列

为了突出“循环”二字,我们将这种顺序队列画成一个圆:

循环队列

循环队列的 rearfront 能够在队列中一圈一圈地转,像钟表的时针和分针一样。不会再出现不能利用的空间了。

【空队列】:队列中没有元素,如上图。

请注意,空队列的条件并不是 front = rear = 0,比如一个空队列经过 3 次入队和 3 次出队操作后仍为空队列:

空队列

所以,循环队列为空队列时,条件应该为 front = rear

【满队列】:队列中没有空闲

满队列

上图是一个最大容量为 8 的空队列,入队 7 个元素后,队列中还剩 1 个空闲位置,如果此时我们再入队 1 个元素:

是满队列吗?

此时队列中确实没有空闲空间了,但注意,此时队列满足了 rear = front ,但满足 rear = front的队列不应该是空队列吗?

这就产生误会了。

不如我们退一步海阔天空,少用一个元素,借此来消除误会。如下图,规定这样是一个满队列。

满队列

我们规定,front 出现在 rear 的下一个位置时,队列为满队列

比如在上图的满队列中, front = 3rear = 2 的下一个位置。

所以队列为满队列的判定条件为:rear + 1 = front,但这的条件是不准确的

因为循环队列中的 frontrear 都是循环使用的,就像钟表的时针一样,所以我们仅根据下标的大小来判断位置是不合理的。下面两个均是满队列,右图不满足rear + 1 = front

img

就像钟表的时针满 12 归零一样,frontrear 也应该满某个数后归零,这个数就是 MAXSIZE

比如 rear = 7 时,如果按平常做法来 ,下一步应该是 rear = 8,但在这里,我们让其归零,所以下一步应该是 rear = 0

用数学公式来表示上面的归零过程就是:rear % MAXSIZE

所以满队列的判断条件应该为:(rear + 1) % MAXSIZE = front

初始时,队列为空,f=r=0,始终记住,f是队头,r是队尾。

  1. 进队:在队未满的情况下,r=(r+1)%m,然后再q[r]=x;

  2. 出队:在队非空的情况下,f=(f+1)%m

  3. 队满的判断:(r+1)%m==f 想在队尾入队,却遇到了队头,肯定已经满了

  4. 队空的判断:r==f 队头队尾重叠,队为空

  5. 思考:

    1. 一个顺序队列为空的判断条件是( )
      A. f+1=r B. r+1=f C. f=0 D. f=r

    2. 设循环队列的数组下标范围是1~n,其头尾指针分别是f和r,则其元素个数为()
      A. r-f B. r-f+1 C. (r-f)%n+1 D. (r-f+n)%n

**【非空非满队列】**很好理解,不再赘述。

const int maxn=1e5;
int que[maxn],front=0,rear=0;
void push(int x)
{	
	que[rear]=x;
	rear=(rear+1)%n;
}
int pop()
{
	int x=que[front];
	front=(front+1)%n;
	return x;
}
int getFront()
{
	return que[front];
}
int getRear()
{
	return que[(rear-1+n)%n];
}
bool isEmpty()
{
	return front==rear;
}
bool isFull()
{
	return (read+1)%n==front;
}

5.双端队列

双端队列(deque)是普通队列的扩展,是指允许两端都可以进行入队和出队操作的队列(即可以在队头进行入队和出队操作,也可以在队尾进行入队和出队操作的队列)。

将队列的两端分别称为前端和后端,两端都可以入队和出队。

在这里插入图片描述

在这里插入图片描述

    1. 我们一般使用STL中的deque来实现。如果要用数组模拟的话,非常困难,涉及到平衡树、静态链表等知识。
deque<int>q;
q.push_back(x); // 两头都可以操作,所以要分前后
q.push_front(x);
q.pop_back();
q.pop_front();
q.front();
q.back():
q.size();
q.empty();

6.优先队列

优先队列(priority_queue)是一个拥有权值观念的queue。它允许在底端添加元素、在顶端去除元素、删除元素。 缺省情况下,优先队列利用一个大顶堆(小根堆)完成。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。

priority_queue<Type, Container, Functional>
其中, Type 为数据类型. Container 为保存数据的容器. Functional 为元素比较的方式.
若不写后面两个参数.容器 默认使用 vector比较方式 默认使用是大顶堆. 队头元素最大
eg:	
#include <queue>
priority_queue<int> q;
priority_queue<string> q;
priority_queue<pair<int,int> > q;
priority_queue<int, vector<int>, greater<int> > p;//小根堆
priority_queue<int, vector<int>, less<int> > p;//大根堆
函数说明接口说明
priority_queue构造空的优先队列
q.top()返回priority_queue的首元素
q.push(x)向priority_queue中加入一个元素
q.size()返回priority_queue当前的长度(大小)
q.pop()从priority_queue队头删除一个元素
q.empty()返回priority_queue是否为空,1为空、0不为空
//自定排序方式的优先队列
#include<bits/stdc++.h >
#include<queue>
using namespace std;
struct Node{
    int x,y;
    Node(int a=0, int b=0):
        x(a), y(b) {}
};

struct cmp{//自定义优先级
    bool operator()(Node a, Node b){
        if(a.x == b.x)  return a.y>b.y;
        return a.x>b.x;
    }
};

int main(){
    priority_queue<Node, vector<Node>, cmp>p;
    for(int i=0; i<10; ++i)
        p.push(Node(rand(), rand()));

    while(!p.empty()){
        cout<<p.top().x<<' '<<p.top().y<<endl;
        p.pop();
    }
    return 0;

7.单调队列

是队列内元素保持一定单调性(单调递增或单调递减)的队列。这里的单调递增或递减是指的从队首到队尾单调递增或递减。既然是队列,就满足先进先出的特点。

详见博客

8.思考题

① 元素R1,R2,R3,R4,R5依次入栈。如果第1个出栈的是R3,那么第5个出栈的不可能是( ) A. R1 B. R2 C. R4 D. R5

② 有6个元素FEDCBA依次入栈,在进栈过程中会有元素弹出栈,下列哪一个不可能是合法的出栈序列? A. EDCFAB B. EDCABF C. CDFEBA D. BCDAEF

③ 有6个元素ABCDEF依次入栈,出栈顺序是BDCFEA,那么栈容量至少应该是( )A. 6 B. 5 C. 4 D. 3 E. 2

④ 地面上有标号为A、B、C的3根细柱,在A柱上方有10个直径相同中间有孔的圆盘,从上到下编号为1,2,3,…,将A柱上的部分盘子经过B柱移入C柱,也可以在B柱上暂存。如果B柱的操作记录为:“进,进,出,进,进,出,出,进,进,出,进,出,出”。那么在C柱上从下到上的盘子编号是( )A. 2 4 3 6 5 7 B. 2 4 1 3 5 7 C. 2 4 3 1 7 6 D. 2 4 3 6 7 5

⑤ 某个车站呈狭长形,宽度只能容下一台车,并且只有一个出入口,已知某时刻该车站状态为空,从这一时刻开始的出入记录为:进,出,进,进,进,出,出,进,进,进,出,出。假设车辆入站的顺序是1,2,3, …,则车辆出栈的顺序是( ) A. 1,2,3,4,5 B. 1,2,4,5,7 C. 1,4,3,7,6 D. 1,4,3,7,2

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值