队列:先进后出
1.模拟队列
const intN=100010;
int q[N],head,tail;
int k;
//队列进入
cin>>k;
q[++tail]=k;
//队列弹出
int n=q[head++];
2.有关队列的stl
只有队头和队尾有操作,
不允许遍历
3.例题:
用模拟队列(最原始的方法,暴力查找)
#include <iostream>
using namespace std;
const int N=10010;
int mem[N],head,tail;
int idx;
int m,n;
void init(){
head=0;
tail=m-1;
idx=0;
}
void insert(int word){
if(idx<=tail){
mem[idx++]=word;
}
else{
head++;
tail++;
mem[idx++]=word;
}
}
int main(){
for(int i=0;i<N;i++){
mem[i]=-1;//洛谷可能有0的数据点
}
cin>>m>>n;
init();
int query=0;
for(int k=0;k<n;k++){
int word;
cin>>word;
bool flag=0;
for(int i=head;i<=tail;i++){
if(mem[i]==word){
flag=1;
}
}
if(flag==1) continue;
insert(word);
query++;
}
cout<<query;
return 0;
}
当然模拟队列,还可以有更多优雅的方式:
#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
int n,m,x,ans,l,r,a[1005],b[1005];//a为标记数组,b为队列数组
//直接让x为a数组索引值
//b数组为记录队列的数组
int main()
{
cin>>m>>n;
l=0;r=0;
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
if (a[x]==0)
{
ans++;//索引次数+1
r++;//队尾++
b[r]=x;//队尾元素=x
a[x]=1;//标记使用
if (r>m) {l++;a[b[l]]=0;}//说明越界,让队头元素弹出
}
}
cout<<ans;
return 0;
}
用stl也可以做:(查找可以利用一个hash【】,做标记)
#include<bits/stdc++.h>
using namespace std;
int Hash[1003]={0}; //用哈希检查内存中有没有单词,hash[i]=1表示单词i在内存中
queue<int> mem; //用队列模拟内存
int main(){
int m,n; scanf("%d%d",&m,&n);
int cnt = 0; //查词典的次数
while(n--){
int en; scanf("%d",&en); //输入一个英文单词
if(!Hash[en]){ //如果内存中没有这个单词
++cnt;
mem.push(en); //单词进队列,放到队列尾部
Hash[en]=1; //记录内存中有这个单词
while(mem.size()>m){ //内存满了
Hash[mem.front()] = 0; //从内存中去掉单词
mem.pop(); //从队头去掉
}
}
}
printf("%d\n",cnt);
return 0;
}
还可以用循环队列:
#include<bits/stdc++.h>
#define N 1003 //队列大小,这里是保证在循环区间内
int Hash[N]={0}; //用Hash检查内存中有没有单词
struct myqueue{
int data[N]; //分配静态空间
/* 如果动态分配,这样写: int *data; */
int head, rear; //队头、队尾
bool init(){ //初始化
/*如果动态分配,这样写:
Q.data = (int *)malloc(N * sizeof(int)) ;
if(!Q.data) return false; */
head = rear = 0;
return true;
}
int size(){ return (rear - head + N) % N;} //c++特性,防止取模之后出现负数
//返回队列长度,由于rear指向的永远是下一个没有值空位置,所以长度要这么表示
bool empty(){ //判断队列是否为空
if(size()==0) return true;
else return false;
}
bool push(int e){ //队尾插入新元素。新的rear指向下一个空的位置
if((rear + 1) % N == head ) return false; //队列满
data[rear] = e;
rear = (rear + 1) % N;//取模与N,保证在循环区域内
return true;
}
bool pop(int &e){ //删除队头元素,并返回它
if(head == rear) return false; //队列空
e = data[head];
head = (head + 1) % N;
return true;
}
int front(){ return data[head]; } //返回队首,但是不删除
}Q;
int main(){
Q.init(); //初始化队列
int m,n; scanf("%d%d",&m,&n);
int cnt = 0;
while(n--){
int en; scanf("%d",&en); //输入一个英文单词
if(!Hash[en]){ //如果内存中没有这个单词
++cnt;
Q.push(en); //单词进队列,放到队列尾部
Hash[en]=1;
while(Q.size()>m){ //内存满了
int tmp; Q.pop(tmp); //删除队头
Hash[tmp] = 0; //从内存中去掉单词
}
}
}
printf("%d\n",cnt);
return 0;
}
4.双端队列
可以在两端进行插入和删除的队列。
手搓一个双端队列与1.模拟队列类似,不赘述
介绍stl:
deque<Type> dq;
dq.front();//返回队头
dq.back();//返回队尾
dq.pop_back();//删除队尾,不返回值
dq.pop_front();//删除队头,不返回值
dq.push_back(k);//在队尾添加元素k
dq.push_front(k);//在队头添加一个元素k
题目1:
思路分析:
我们类似于单调栈:
这样就可以保证:每次在在队列的框中,头元素一定是整个队列最小的。
流程如下:
-
由于此时队中没有一个元素,我们直接令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=1e6+10;
#include <deque>
deque<int> dq;
int a[N];
int n,m;
int main(){
scanf("%d%d",&n,&m);
//最小值
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++){
while(dq.size()&&a[dq.back()]>a[i]) dq.pop_back();
dq.push_back(i);
if(i>=m){
if(dq.front()<=i-m&&dq.size()) dq.pop_front();
cout<<a[dq.front()]<<" ";
}
}
cout<<endl;
dq.erase(dq.begin(),dq.end());
//最大值
for(int i=1;i<=n;i++){
while(dq.size()&&a[dq.back()]<a[i]) dq.pop_back();
dq.push_back(i);
if(i>=m){
if(dq.front()<=i-m&&dq.size()) dq.pop_front();
cout<<a[dq.front()]<<" ";
}
}
return 0;
}
一个会超时的lj代码(忽略私用)
#include <iostream>
using namespace std;
#include <queue>
#include<vector>
priority_queue<int,vector<int>,greater<int>> q_min;
queue<int> q;
vector<int> v;
vector<int> min_num;
vector<int> max_num;
int main() {
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++){
int m;
cin>>m;
v.push_back(m);
}
for (int i=0;i<k;i++){
q.push(v[i]);
}
//find the min
for(int i=k;i<=n;i++){
queue<int> q_temp;
while (!q.empty()) {
q_min.push(q.front());
q_temp.push(q.front());
q.pop();
}
min_num.push_back(q_min.top());
while (q_min.size()!=1) {
q_min.pop();
}
max_num.push_back(q_min.top());
q_min.pop();
//update the new,and pop the old
while (!q_temp.empty()) {
q.push(q_temp.front());
q_temp.pop();
}
q.pop();
q.push(v[i]);
}
for(auto it=min_num.begin();it!=min_num.end();it++){
cout<<*it<<" ";
}
cout<<endl;
for(auto it=max_num.begin();it!=max_num.end();it++){
cout<<*it<<" ";
}
return 0;
}
2.题目2
Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.
Input
The first line of the input contains an integer T(1<=T<=20) which means the number of test cases. Then T lines follow, each line starts with a number N(1<=N<=100000), then N integers followed(all the integers are between -1000 and 1000).
Output
For each test case, you should output two lines. The first line is "Case #:", # means the number of the test case. The second line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If there are more than one result, output the first one. Output a blank line between two cases.
就是求最大子序列的和。
(会的话)可以利用提供贪心或者dp去做,但这里只展示用单调队列去求。(但是涉及到一些动态规划方面)
#include<bits/stdc++.h>
using namespace std;
deque<int> dq;
int s[100005];
int main(){
int n,m; scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&s[i]);
for(int i=1;i<=n;i++) s[i]=s[i]+s[i-1]; //计算前缀和
int ans = -1e8;
dq.push_back(0);
for(int i=1;i<=n;i++) {
while(!dq.empty() && dq.front()<i-m) dq.pop_front(); //队头超过m范围:删头
if(dq.empty()) ans = max(ans,s[i]);
else ans = max(ans,s[i]-s[dq.front()]); //队头就是最小的s[k]
while(!dq.empty() && s[dq.back()]>=s[i]) dq.pop_back();//队尾大于s[i],去尾
dq.push_back(i);
}
printf("%d\n",ans);
return 0;
}
5.优先队列:
优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的
和队列基本操作相同:
top 访问队头元素
empty 队列是否为空
size 返回队列内元素个数
push 插入元素到队尾 (并排序)
emplace 原地构造一个元素并插入队列
pop 弹出队头元素
swap 交换内容
定义:
priority_queue<Type, Container, Functional>
这三个参数,后面两个可以省略,第一个不可以。
其中:
type:数据类型;
container:实现优先队列的底层容器;
function:元素之间的比较方式;
对于container,要求必须是数组形式实现的容器,例如vector、deque,而不能使list。
在STL中,默认情况下(不加后面两个参数)是以vector为容器,以 operator< 为比较方式,所以在只使用第一个参数时,优先队列默认是一个最大堆,每次输出的堆顶元素是此时堆中的最大元素。
后面的是一个仿函数,就是要自己去写排序,一般默认的是大根堆。
//默认都是less
sort(vec.begin(),vec.end(),less<int>()); //内置类型从小到大 升序
priority_queue <int,vector<int>,less<int> > q1; //top出数据从大到小 降序
sort(vec.begin(),vec.end(),greater<int>()); //内置类型从大到小 降序
priority_queue <int,vector<int>,greater<int> > q2; //top出数据从小到大 升序
具体的重载运算符:(这篇很全)