队列T_T

本文介绍了队列在算法中的几种应用,包括普通队列、单调队列和优先队列。详细讨论了如何使用队列解决Acwing模拟队列、周末舞会、Blah数集、圆圈报数和连通块等问题,并给出了相应的代码实现。同时,文章还讲解了单调队列的特点和在滑动窗口、逛画展和切蛋糕问题中的应用,以及优先队列的定义、操作和在合并果子问题中的使用。
摘要由CSDN通过智能技术生成

 【定义】

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表(先进先出)。

例1 Acwing 模拟队列

【分析】

数组实现

队尾加入元素:尾指针向后挪一位 再赋值(++tail)

弹出队头元素:首指针向后挪一位 原来位置上的值自然就弹出了(head++)

查询是否为空:当首指针小于等于尾指针时 不为空 否则为空

查询队头元素:直接输出即可

【代码】

#include<bits/stdc++.h>
#define N 100005
using namespace std;
int a[N];
int head,tail;//定义首尾指针 
string s;
int main(){
	int m;
	cin>>m;
	head=0,tail=-1;
	while(m--){
		cin>>s;//输入字符串 
		if(s=="push"){
			int x;
			cin>>x;
			a[++tail]=x;//队尾加入元素 
		}
		if(s=="pop") head++;//弹出队头元素 
		if(s=="empty"){
			if(head<=tail) cout<<"NO"<<endl;
			else cout<<"YES"<<endl;
		}
		if(s=="query") cout<<a[head]<<endl;
	}
	return 0;
}

例2 OJ332 周末舞会

【分析】

定义两个队列,一边男一边女,将每个人所代表的编号放入队列。将队首元素循环放入队尾,然后弹出,循环输出每次出列即可。

【代码】

#include <bits/stdc++.h>
using namespace std;
queue<int> q1,q2;
int a,b,n;
int main(){
	cin>>a>>b;
	for(int i=1;i<=a;i++) q1.push(i);
	for(int i=1;i<=b;i++) q2.push(i);
	cin>>n;
	while(n--){
		cout<<q1.front()<<" "<<q2.front()<<endl;
		q1.push(q1.front());q1.pop();
		q2.push(q2.front());q2.pop();
	}
}

例3 OJ1333 Blah数集

【分析】

输入a和n后,先判断n是否为1,如果为1,直接输出a即可。

否则,定义两个队列分别存放2*a+1和3*a+1的值。通过列举可得,两个队列都具有单调性。因而从2开始循环到n,每一次将两个队首元素进行比较,令tt等于较小元素后再弹出较小元素(相等的情况下两个都弹出),进行下一次比较,直到全部遍历完,输出。

【代码】

#include <bits/stdc++.h>
using namespace std;
int main(){
	long long a,n;
	while(cin>>a>>n){
		if(n==1){
			cout<<a<<endl;
			continue;
		}//n=1时需要特判
		else{
			queue<long long> qa,qb;
			int ta,tb,tt;
			qa.push(2*a+1);
			qb.push(3*a+1);//放入队列
			for(int i=2;i<=n;i++){
				ta=qa.front();
				tb=qb.front();
				if(ta==tb){
					tt=ta;
					qa.pop();
					qb.pop();
				}
				else if(ta<tb){
					tt=ta;
					qa.pop();
				}
				else{
					tt=tb;
					qb.pop();
				}//比较
				qa.push(tt*2+1);
				qb.push(tt*3+1);//下一次循环
			}
		cout<<tt<<endl;	
		}
		
	}
	return 0;
}

例4 OJ334 圆圈报数

【分析】

约瑟夫问题

【代码】

#include <bits/stdc++.h>
using namespace std;
queue<int> q;
int a[105];
int main(){
	int n,m,f;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		q.push(i);
	}//将下标存入队列 
	for(int i=1;i<=n;i++){
		f=1;//从1开始 
		while(f<m){
				q.push(q.front());
				q.pop();
				f++;	
		}
		cout<<q.front()<<" ";
		q.pop();
	}
	return 0;
}

例5 OJ335 连通块

【分析】

广搜

【代码】

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
int xx[4]={-1,0,1,0};
int yy[4]={0,1,0,-1};
int a[105][105];
int n,m;
void bfs(int x,int y){
	queue<PII> q;
	q.push({x,y});
	while(q.size()!=0){
		PII hhh;
		hhh=q.front();
		for(int i=0;i<4;i++){
			int kx,ky;
			kx=hhh.first+xx[i];
			ky=hhh.second+yy[i];
			if(kx<=0||kx>n||ky<=0||ky>m||a[kx][ky]==0) continue;
			q.push({kx,ky});
			a[kx][ky]=0;//将走过的标记为0
		}
		q.pop();//
	}
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			cin>>a[i][j];
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(a[i][j]==1){
				ans++;
				bfs(i,j);
			}
		}
	}
	cout<<ans;
	return 0;
}

【拓展】

pair

pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

如果定义多个相同的pair类型对象,可以使用typedef简化声明。

直接用firstsecond访问

二.单调队列

【定义】

具有单调性质队列性质的数据结构。 

【特点】

时间复杂度为O(n) 。 单调队列可以保证,对于任何一个数而言,只会在队列中出现一次,一旦这个数对于最后答案没有贡献了,就会及时地将它删除。

例1 Acwing 滑动窗口

【分析】

1.需要输出最小值和最大值,首先考虑到单调性。

2.窗口不断移动,每次需要解决出窗口的队首元素。

3.当队列中的数不满足单调性时,及时弹出并更新最大(最小)值。

4.最大和最小值分别求时,中间一定要重置head和tail。

【代码1 数组模拟】

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
struct ST{
	int x,i;
}; //x为每一次进来的数,i用来表示下标
ST q[N];
int head,tail;
int n,k;
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	head=1,tail=0;
	for(int i=1;i<=n;i++){
	    if(head<=tail&&i-q[head].i>=k) head++;//队列不为空且两数间长度大于等于窗口长度,不断移动
	    while(head<=tail&&q[tail].x>a[i]) tail--;//当进来的数比当前队尾的数小,弹出不需要的数
	    tail++;
	    q[tail].x=a[i];
	    q[tail].i=i;//赋值
	    if(i>=k) printf("%d ",q[head].x);
	}//维护单调递增的队列,输出最小值
	cout<<endl;
	head=1,tail=0;
	for(int i=1;i<=n;i++){
	    if(head<=tail&&i-q[head].i>=k) head++;
	    while(head<=tail&&q[tail].x<a[i]) tail--;//当进来的数比当前队尾的数大,弹出不需要的数
	    tail++;
	    q[tail].x=a[i];
	    q[tail].i=i;
	    if(i>=k) printf("%d ",q[head].x);
	}//维护单调递减的队列,输出最大值
return 0;
}

【代码2 双端队列】

#include <iostream>
#include <cstdio>
#include <deque>
using namespace std;
const int N=1e6+10;
int a[N];
struct ST{
	int x,i;
};
deque<ST> q;
int main(){
	int n,k;
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]); 
	}
	for(int i=1;i<=n;i++){
		if(q.size()&&i-q.front().i>=k) q.pop_front();
		while(q.size()&&q.back().x>a[i]) q.pop_back();	
		ST t;
		t.x=a[i];
		t.i=i;
		q.push_back(t);
		if(i>=k) printf("%d ",q.front().x);
	}
	cout<<endl;
	while(q.size()) q.pop_front();
	for(int i=1;i<=n;i++){
		if(q.size()&&i-q.front().i>=k) q.pop_front();
		while(q.size()&&q.back().x<a[i]) q.pop_back();	
		ST t;
		t.x=a[i];
		t.i=i;
		q.push_back(t);
		if(i>=k) printf("%d ",q.front().x);
	}
	return 0;
}

 例2 Acwing 逛画展

【分析】

需要看过所有名家的画,首先让每一幅名画入队,同时画家数++。对于一幅名画,如果先前有和它属于同一个画家的画,那么先前的画就不需要了。这样从左往右找,直到找到最小值出现的区间。

【代码】

#include <iostream>
#include <cstdio>
#include <deque>
using namespace std;
const int N=1e6+5;
int a[N],q[2005];//a用来存画作,q用来存画家数
int ans=1e7;
int hh,tt,la,lb;
int main(){
    int n,m;
    cin>>n>>m;
    int cnt=0;
    hh=1,tt=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(q[a[i]]==0) cnt++;//画家数++
        q[a[i]]++;//画作数++
        tt++;
        while(q[a[hh]]>=2){//当一个画家的画作出现两次以上
        q[a[hh]]--;//不需要
        hh++;
        }
        if(cnt>=m&&tt-hh+1<ans){//当画家数等于m且有最小区间
        ans=tt-hh+1;
        la=hh;
        lb=tt;//此时的hh和tt分别为左端点和右端点
        }
    }
    cout<<la<<" "<<lb;
    return 0;
}

例3 Acwing 切蛋糕 

 

 

 【代码】

#include <iostream>
#include <cstdio>
using namespace std;
const int N=1e6+10;
struct ST{
    int x,i;
}q[N];
int a[N],s[N];
int hh,tt,n,m;
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }//求前缀和
    hh=1,tt=0;
    int ans=0;
    q[++tt].x=0;
    q[tt].i=0;//赋初值便于求前缀和
    for(int i=1;i<=n;i++){//维护一个单调递增的队列
        if(hh<=tt&&i-q[hh].i>m) hh++;
        while(hh<=tt&&q[tt].x>=s[i]) tt--;
        tt++;
        q[tt].x=s[i];
        q[tt].i=i;//更新最大值
        if(hh<=tt ){
        ans=max(ans,s[i]-q[hh].x);
        }
    }
    cout<<ans;
}

三.优先队列

【定义】

具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的。

【特点】

队首元素一定是优先级最高的那个,可以在任何时候往优先级队列中加入元 素,而优先级队列底层的数据结构是堆会随时调增结构,使得每次的队首元素都是优先级最大的。

【操作】

  • top 访问队头元素
  • empty 队列是否为空
  • size 返回队列内元素个数
  • push 插入元素到队尾 (并排序)
  • emplace 原地构造一个元素并插入队列
  • pop 弹出队头元素
  • swap 交换内容

 【使用】

priority_queue<typename>  name;//大根堆

priority_queue<typename,vector<typename>,greater<typename> > name;//小根堆

结构体的优先级设置

struct fruit{
    string name;
    int price;
    friend bool operator < (fruit f1, fruit f2) {
        return f1.price < f2.price;//表明f2.price的优先级更高 因而价格高的在前面
    }
}

例1 OJ1369 合并果子

【代码】

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int a[30005];
priority_queue<int,vector<int>,greater<int> > q;
int main(){
	int n,k;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>k;
		q.push(k); 
	}
	int a,b,ans=0;
	while(q.size()>=2){
			a=q.top();q.pop();
			b=q.top();q.pop();
			ans+=a+b;
			q.push(a+b);
	}
	cout<<ans;
	return 0; 
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值