STL分类
栈和队列基本概念
栈(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
可以是任何基本类型,例如int
、double
、char
、结构体等,也可以是STL
标准容器,例如vector
、set
、queue
等。 -
函数说明 接口说明 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 = 0
,rear = 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
可以是任何基本类型,例如 int
、double
、char
、结构体等,也可以是STL
标准容器,例如vector
、set
、queue
等。
函数声明 | 接口说明 |
---|---|
queue() | 构造空的队列 |
empty() | 检测队列是否为空,是返回true,否则返回false |
size() | 返回队列中有效元素的个数 |
front() | 返回队头元素的引用 |
back() | 返回队尾元素的引用 |
push() | 在队尾将元素val入队列 |
pop() | 将队头元素出队列 |
上图也出现了什么问题?
满队列的是空间用尽,不能再插入元素的队列,虽然图5的队列也不能继续插入元素了,但它还有剩余空间,所以这样的队列还不能称之为满队列,可称之为假满队列。
怎么解决呢?报错是 rear
越界导致,而队列的前大部分都是空闲的,所以当 rear
越界时,我们可不可以将其移动到下标 0 处呢?
显然是可以的,这样就构成了一个“循环”,我们称这种 front
和 rear
可以循环利用的队列为循环队列。
4.循环队列
为了突出“循环”二字,我们将这种顺序队列画成一个圆:
循环队列的 rear
和 front
能够在队列中一圈一圈地转,像钟表的时针和分针一样。不会再出现不能利用的空间了。
【空队列】:队列中没有元素,如上图。
请注意,空队列的条件并不是 front = rear = 0
,比如一个空队列经过 3 次入队和 3 次出队操作后仍为空队列:
所以,循环队列为空队列时,条件应该为 front = rear
【满队列】:队列中没有空闲
上图是一个最大容量为 8 的空队列,入队 7 个元素后,队列中还剩 1 个空闲位置,如果此时我们再入队 1 个元素:
此时队列中确实没有空闲空间了,但注意,此时队列满足了 rear = front
,但满足 rear = front
的队列不应该是空队列吗?
这就产生误会了。
不如我们退一步海阔天空,少用一个元素,借此来消除误会。如下图,规定这样是一个满队列。
我们规定,front
出现在 rear
的下一个位置时,队列为满队列。
比如在上图的满队列中, front = 3
在 rear = 2
的下一个位置。
所以队列为满队列的判定条件为:rear + 1 = front
,但这的条件是不准确的。
因为循环队列中的 front
和 rear
都是循环使用的,就像钟表的时针一样,所以我们仅根据下标的大小来判断位置是不合理的。下面两个均是满队列,右图不满足rear + 1 = front
:
就像钟表的时针满 12 归零一样,front
和 rear
也应该满某个数后归零,这个数就是 MAXSIZE
。
比如 rear = 7
时,如果按平常做法来 ,下一步应该是 rear = 8
,但在这里,我们让其归零,所以下一步应该是 rear = 0
。
用数学公式来表示上面的归零过程就是:rear % MAXSIZE
所以满队列的判断条件应该为:(rear + 1) % MAXSIZE = front
。
初始时,队列为空,f=r=0,始终记住,f是队头,r是队尾。
-
进队:在队未满的情况下,r=(r+1)%m,然后再q[r]=x;
-
出队:在队非空的情况下,f=(f+1)%m
-
队满的判断:(r+1)%m==f 想在队尾入队,却遇到了队头,肯定已经满了
-
队空的判断:r==f 队头队尾重叠,队为空
-
思考:
-
一个顺序队列为空的判断条件是( )
A. f+1=r B. r+1=f C. f=0 D. f=r -
设循环队列的数组下标范围是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)是普通队列的扩展,是指允许两端都可以进行入队和出队操作的队列(即可以在队头进行入队和出队操作,也可以在队尾进行入队和出队操作的队列)。
将队列的两端分别称为前端和后端,两端都可以入队和出队。
-
- 我们一般使用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