文章目录
一、队列的概念
这就是现实生活中的队列。
队列是一种特殊的线性表,其插入操作限定在表的一端进行,称为“入队”;其删除操作则限定在表的另一端进行,称为“出队”。插入一端称为队尾,删除一端称为队首。因此,队列也被称作“先进先出”的线性表(FIFO,First In First Out)。
二、队列的操作
1.定义
queue<typename> 队列名字;
其中typename是想要的数据类型。
哦,对了,要使用队列,还需要加上一个头文件:(加了万能头文件就不需要了)
#include <queue>
如果要存int,可以这么写:
queue<int> q;
2.队列的函数
假设已经定义一个队列,名字是q,那么就有这些函数:
q.front(); //获取队首
q.back(); //获取队尾
q.push(x); //插入元素,x表示要插入的值,什么都行(但是类型必须和定义的相同)
q.pop(); //将队头弹出,无返回值
q.size(); //返回队列里有多少个元素
q.empty(); //如果队列为空,返回true,否则返回false( 等同于q.size()==0 )
q.swap(q2); //交换q和q2里面的值(q2需要和q是一个类型)
queue默认是空队列,如果不想怎么办?
假设已经定义了q1是queue<int>类型,那么可以这么写:
queue<int> q2(q1);
举个栗子:
#include <iostream>
#include <queue>
using namespace std;
int main(int argc,char* argv[],char** env){ //不要问我为什么main要写成这样
queue<int> q;
for(int i=1;i<=10;i++)
q.push(i);
while(!q.empty()){ //队列非空就继续循环
cout<<q.front()<<' '; //每次都输出队首
q.pop(); //输出一个就删一个
}
return 0;
}
三、其他队列
1.双向队列
双向队列不仅可以在队尾插入,在队头删除,还可以在队头插入,在队尾删除(还有很多),还支持下标访问!这比数组要强多了。(但是慢)
①.定义方法
deque<typename> 双向队列名字;
其中typename是想要的数据类型。
哦,对了,要使用双向队列,还需要加上一个头文件:(加了万能头文件就不需要了)
#include <deque>
或
#include <queue>
因为在queue这个头文件里面包含了deque
如果要存int,可以这么写:
deque<int> q;
②.函数
假设已经定义一个双向队列,名字是q,那么就有这些函数:
q.push_front(x); //在队头插入元素,x表示要插入的值,什么都行(但是类型必须和定义的相同)
q.push_back(x); //在队尾插入元素,x表示要插入的值,什么都行(但是类型必须和定义的相同)
q.pop_front(); //将队头弹出,无返回值
q.pop_back(); //将队尾弹出,无返回值
q.front(); //和queue的一样
q.back(); //和queue的一样
q.size(); //和queue的一样
q.empty(); //和queue的一样
q.swap(q2); //和queue的一样
q[i] //获取第i个元素(队头是第0个)
q.at(i); //同上(不同的地方很少)
q.clear(); //清空双向队列
q.begin(); //获取首地址,返回迭代器 (待会说怎么用)
q.end(); //获取位地址+1 注意!还要加1
还有很多......
deque默认是空队列,如果不想怎么办?
假设已经定义了a是int[]类型,那么可以这么写:
deque<int> q(a,a+想要放进去的个数);
举个栗子:
#include <iostream>
#include <deque>
using namespace std;
int main(int argc,char* argv[],char** env){
deque<int> q;
for(int i=1;i<=5;i++)
q.push_back(i);
cout<<q.size()<<endl; //5
// cout<<q.at(6)<<endl; //运行会错误
for(int i=0;i<5;i++)
cout<<q[i]<<' '; //1 2 3 4 5
return 0;
}
用迭代器遍历:
#include <iostream>
#include <deque>
using namespace std;
int main(int argc,char* argv[],char** env){
deque<int> q;
for(int i=1;i<=5;i++)
q.push_back(i);
deque<int>::iterator it; //声明迭代器
for(it=q.begin();it<q.end();it++)
cout<<*it<<' '; //1 2 3 4 5
return 0;
}
2.优先队列
优先队列保证队头最大(想最小也行)但内部非有序(你是看不着的,因为每删掉一priority_queue都会把最大(或最小)的放在最前面)
①.定义方法
priority_queue<typename> 优先队列名字;
其中typename是想要的数据类型。
哦,对了,要使用优先队列,还需要加上一个头文件:(加了万能头文件就不需要了)
#include <queue>
如果要存int,可以这么写:
priority_queue<int> q;
②.函数
假设已经定义一个优先队列,名字是q,那么就有这些函数:
q.push(x); //插入元素,x表示要插入的值,什么都行(但是类型必须和定义的相同)
q.pop(); //将队头弹出,无返回值
q.top(); //和queue的一样
q.size(); //和queue的一样
q.empty(); //和queue的一样
如果要从小到大怎么办?
首先,说一下头文件(可以不要,因为queue里包含了)
#include <vector>
#include <functional>
然后,再说一下另一种声明方法:
priority_queue<类型,容器类型,比较器> q;
其中,比较器可以是greater<类型>(小到大)或less<类型>(大到小),容器必须是用数组实现的容器,比如:vector<类型>、deque<类型>但不能是list
我个人觉得很奇怪,反正是要倒着写
还有一种比较器,是自定义的:
struct cmp{
bool operator()(const int &x,const int &y){
return x>=y; //倒着写
}
};
priority_queue<int,vector<int>,cmp> q;
举个栗子:
#include <iostream>
#include <queue>
using namespace std;
int main(int argc,char* argv[],char** env){
priority_queue< int , vector<int> ,greater<int> > q; //小到大
q.push(1);
q.push(3);
q.push(2);
q.push(4);
q.push(5);
while(!q.empty()){ //输出
cout<<q.top()<<' '; //1 2 3 4 5 但内部并非有序!只不过每pop一个,
q.pop(); //都会重新找最小的放第一个。
}
return 0;
}
四、队列的应用(代码在最后)
约瑟夫环
分析:其实可以想象成一个队列,每个人出队后再入队,(除非数到m)
要求的是出队的顺序,所以可以每出队一个,就输出一个,直到只剩一个(最后一个肯定要出队)
海港
分析:这道题千万不能存船,要存人头。可以用一个数组来记24小时内每个国籍有多少人,并维护一个队列,来记录24小时内的人分别是谁,还可以在读入的同时,弄一个计数器表示有多少不同的国籍,再弄一个循环,把24小时外的去掉,如果那个国籍的人都没了,就让计数器减1,最后输出计数器就行了。
滑动窗口
(【模板】单调队列 )
分析(很麻烦):先想最小,可以用一个优先队列,小的在前面,大的在后面。把要出队的出队。
但是怎么才能知道要不要出队?有两个方法:1.查找 2.直接存下标(我用的是存下标,比较器就需要改一改了),判断当前元素下标是不是小于当前要插的是第几个元素-窗口+1,最后输出第一个就行了(因为优先队列保证第一个元素最小)最大的差不多。
赶紧写一写试试吧!
写完了,或不会写了,就往下看吧。
代码在这里↓
约瑟夫环:
#include <iostream>
#include <queue>
using namespace std;
int main(int argc,char* argv[],char** env){
queue<int> q;
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
q.push(i);
int cnt=0; //报数报到几了
while(q.size()>1){ //为什么讲过了
cnt++; //报数
if(cnt<m){ //如果不应该出队
q.push(q.front());
}else{
cnt=0; //清0
cout<<q.front()<<' '; //要出队,输出
}
q.pop();
}
cout<<q.front()<<endl; //为什么讲过了
return 0;
}
海港:
#include <string.h>
#include <stdio.h>
#include <queue>
#include <time.h>
using namespace std;
#define DAY 86400
struct passenger { //乘客结构体
int country; //国籍
int t; //时间
};
int main(int argc,char* argv[],char** env){
unsigned int mp[100001],ans=0;
memset(mp,0,sizeof(mp)); //初始化
int n;
queue<passenger> q;
scanf("%d",&n);
for(int i=0;i<n;i++){
int time,m;
scanf("%d%d",&time,&m);
for(int j=0;j<m;j++){
passenger tmp;
scanf("%d",&tmp.country);
tmp.t=time;
q.push(tmp); //读入的同时扔进队列
if(mp[tmp.country]==0) //新国籍
ans++; //计数器++
mp[tmp.country]++; //这个国籍多了一个人,+1
}
while(time-DAY>=q.front().t&&!q.empty()){ //超过24小时了
int tmp=q.front().country;
mp[tmp]--; //少一个人,-1
if(mp[tmp]==0) //如果没有这个国籍的人了
ans--; //计数器--
q.pop(); //注意!这一步不能少
}
//因为OJ都用的是文件,所以可以每读入一行就可以输出
printf("%d\n",ans); //输出结果
}
return 0;
}
滑动窗口(很麻烦的写法):
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int a[1000000];
struct cmp1 { //小到大
bool operator()(const int &x,const int &y) {
return a[x]>=a[y];
}
};
struct cmp2 { //大到小
bool operator()(const int &x,const int &y) {
return a[x]<=a[y];
}
};
bool check(int begin,int top) { //判断是否该出队
return top<begin;
}
int main(int argc,char* argv[],char** env) {
int n,k; //k:滑动窗口大小 n:数组长度
priority_queue< int , vector<int> , cmp1 > qmin; //存下标
priority_queue< int , vector<int> , cmp2 > qmax; //存下标
vector<int> minn,maxn;
cin>>n>>k;
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=n; i++) {
//把第i个元素插入priority_queue中
qmin.push(i);
qmax.push(i);
//删元素
while(i>k&&check(i-k+1,qmin.top())&&!qmin.empty()) {
qmin.pop();
}
while(i>k&&check(i-k+1,qmax.top())&&!qmax.empty()){
qmax.pop();
}
//把最小、最大加到vector中
minn.push_back(qmin.top());
maxn.push_back(qmax.top());
}
//输出
for(int i=k-1;i<minn.size();i++)
cout<<a[minn[i]]<<' ';
cout<<endl;
for(int i=k-1;i<maxn.size();i++)
cout<<a[maxn[i]]<<' ';
cout<<endl;
return 0;
}
队列的应用还有很多,比如广搜(也可以说是宽搜,BFS),在这里不作讲解了。
886~