队列(模拟+stl)+例题(双端队列,单调队列)

文章介绍了模拟队列、stl中的队列、优先队列(如最大堆和自定义比较),以及如何使用这些数据结构解决实际问题,如机器翻译、滑动窗口和最大子序列和。同时提及了循环队列、双端队列和优先队列的使用方法以及相关算法策略。
摘要由CSDN通过智能技术生成

队列:先进后出 

1.模拟队列

const intN=100010;
int q[N],head,tail;
int k;
//队列进入
cin>>k;
q[++tail]=k;
//队列弹出
int n=q[head++];

2.有关队列的stl

只有队头和队尾有操作,

不允许遍历

3.例题:

[NOIP2010 提高组] 机器翻译 - 洛谷

用模拟队列(最原始的方法,暴力查找)

#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. 由于此时队中没有一个元素,我们直接令1进队。此时,q={1},p={1}。

  2. 现在3面临着抉择。下面基于这样一个思想:假如把3放进去,如果后面2个数都比它大,那么3在其有生之年就有可能成为最小的。此时,q={1,3},p={1,2}

  3. 下面出现了-1。队尾元素3比-1大,那么意味着只要-1进队,那么3在其有生之年必定成为不了最小值,原因很明显:因为当下面3被框起来,那么-1也一定被框起来,所以3永远不能当最小值。所以,3从队尾出队。同理,1从队尾出队。最后-1进队,此时q={-1},p={3}

  4. 出现-3,同上面分析,-1>-3,-1从队尾出队,-3从队尾进队。q={-3},p={4}。

  5. 出现5,因为5>-3,同第二条分析,5在有生之年还是有希望的,所以5进队。此时,q={-3,5},p={4,5}

  6. 出现3。3先与队尾的5比较,3<5,按照第3条的分析,5从队尾出队。3再与-3比较,同第二条分析,3进队。此时,q={-3,3},p={4,6}

  7. 出现6。6与3比较,因为3<6,所以3不必出队。由于3以前元素都<3,所以不必再比较,6进队。因为-3此时已经在滑动窗口之外,所以-3从队首出队。此时,q={3,6},p={6,7}

  8. 出现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出数据从小到大   升序

具体的重载运算符:(这篇很全)

https://blog.csdn.net/weixin_47266712/article/details/126400051?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171184658416800225572850%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=171184658416800225572850&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-2-126400051-null-null.142^v100^pc_search_result_base1&utm_term=%E4%BC%98%E5%85%88%E9%98%9F%E5%88%97&spm=1018.2226.3001.4187

  • 20
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值