当前位置:   article > 正文

C++基础数据结构——队列_c++ 队列

c++ 队列

队列中的数据存取方式是“先进先出”,只能像队尾插入数据,从队头移出数据。队列和栈的主要问题是查找较慢,需要从头到尾一个个的查找。在某些情况下可以用优先队列,让优先级最高(最大的数或者最小的数)先出队列。

竞赛中一般用STL queue或手写静态数组实现队列。本文只介绍STL queue。

队列的主要操作如下

  1. #include<queue>//如果要使用队列的话 必须包含这个头文件
  2. //主要操作如下:
  3. queue<Type>q;//定义队列,Type为数据类型,如int,float,char等
  4. q.push(item);//把item放进队列
  5. q.front();//返回队首元素,但不会删除
  6. q.pop();//删除队首元素
  7. q.back();//返回队首元素
  8. q.size();//返回元素个数
  9. q.empty(); //检查队列是否为空

 下面给出一道例题,用STL queue 实现:https://www.luogu.com.cn/problem/P1540

 

  1. #include<iostream>
  2. #include<queue>
  3. using namespace std;
  4. int Hash[1103]={0};
  5. queue<int> mc;//队列模拟内存
  6. int main()
  7. {
  8. int m,n;
  9. cin>>m>>n;
  10. int ans=0;
  11. while(n--)
  12. {
  13. int a; cin>>a;
  14. if(!Hash[a])//如果内存中没有这个单词
  15. {
  16. ans++;
  17. mc.push(a);
  18. Hash[a]=1;
  19. while(mc.size()>m)//内存满了
  20. {
  21. Hash[mc.front()]=0;
  22. mc.pop();
  23. }
  24. }
  25. }
  26. cout<<ans<<endl;
  27. return 0;
  28. }

下面来接受双端队列和单调队列 

双端队列是一种具有队列和栈性质的数据结构,它能在俩高端进行插入和删除,而且也只能在两端插入和删除。双端队列的用法如下:

  1. #include<deque>//使用时必须包含这个头文件
  2. deque<Type>dq//建立双端队列
  3. dq[i]//返回下标为i的元素
  4. dq.front();//返回队首元素
  5. dq.back();//返回队尾元素
  6. dq.pop_back();//弹出队尾元素
  7. dq.pop_front();//弹出队头元素
  8. dq.push_back();//在队尾插入一个元素
  9. dq.push_front(); //在队首插入一个元素

双端队列的经典应用是单调队列。单调队列中的元素是单调有序的,且元素在队列中的顺序和原来在序列中的顺序是一致的。单调队列的复杂度是O(n)

单调队列与滑动窗口 :https://www.luogu.com.cn/problem/P1886

思路:本体暴力法的话代码很好写:从头到尾扫描,每次检查k个数字,一共检查O(nk)次。暴力法一定会超时。下面用单调队列求解,它的复杂度是O(n)

在本题中,单调队列有一下特征。

(1)队头的元素始终是队列中最小的。需要根据输出队头,但是不一定弹出。

(2)元素只能从队尾进入队列。从队尾和队头都可以弹出。

(3)序列中的每个元素都是必须进入队列。例如,x进入队尾时,和原队尾y比较,如果x<=y,就从队尾弹出y;一直弹出队尾所有比x大的元素,最后x进入队尾。这个入队操作保证了队头元素是队列中最小的。

  1. #include<iostream>
  2. #include<deque>
  3. using namespace std;
  4. const int N=1000005;
  5. int a[N];
  6. deque<int>q;//队列中的数据实际上是元素在原序列中的位置
  7. int main()
  8. {
  9. int n,m; cin>>n>>m;
  10. for(int i=1;i<=n;i++) cin>>a[i];
  11. for(int i=1;i<=n;i++)//输出最小值
  12. {
  13. while(!q.empty()&&a[q.back()]>a[i]) q.pop_back();//去尾
  14. q.push_back(i);
  15. if(i>=m)//每个窗口输出一次
  16. {
  17. while(!q.empty()&&q.front()<=i-m) q.pop_front();//删头
  18. cout<<a[q.front()]<<" ";
  19. }
  20. }
  21. cout<<endl;
  22. while(!q.empty()) q.pop_front();
  23. for(int i=1;i<=n;i++)
  24. {
  25. while(!q.empty()&&a[q.back()]<a[i]) q.pop_back();
  26. q.push_back(i);
  27. if(i>=m)
  28. {
  29. while(!q.empty()&&q.front()<=i-m) q.pop_front();
  30. cout<<a[q.front()]<<" ";
  31. }
  32. }
  33. cout<<endl;
  34. return 0;
  35. }

单调队列与最大子序和问题

什么是子序和?给定长度为n的整数序列A,它的“子序列”定义为A中非空的一段连续匀速。例如,序列(1,2,3,4,5),前4个元素的子序和为10.

最大子序和问题按照子序有无长度限制分为两种。

问题(1):不限制子序列长度。在所有可能的子序列中找到一个子序列,该子序列最大

问题(2):限制子序列的长度,给定一个限制长度为m,找出一段长度不超过m的连续子序列,使它的子序列最大。

问题一的求解:

方法一:贪心法。逐个扫描序列中的元素并累加。加一个正数时,子序和会增加;加一个负数时,子序和会减小。如果当前得到的和变成了负数,这个负数会在接下来的累加中会减小后面的求和,所以抛弃它,从下一位置求和。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int INF=0x7fffffff;
  4. int main()
  5. {
  6. int t; cin>>t;
  7. for(int i=1;i<=t;i++)
  8. {
  9. int n;cin>>n;
  10. int maxsum=-INF;
  11. int start=1,end=1,p=1;
  12. int sum=0;
  13. for(int j=1;j<=n;j++)
  14. {
  15. int a; cin>>a;
  16. sum+=a;
  17. if(sum>maxsum){maxsum=sum;start=p;end=j;}
  18. if(sum<0){
  19. sum=0;
  20. p=j+1;
  21. }
  22. }
  23. printf("Case %d:\n",i);printf("%d %d %d\n",maxsum,start,end);
  24. if(i!=t) cout<<endl;
  25. }
  26. return 0;
  27. }

方法二:动态规划。定义状态dp[i],表示以a[i]结尾的最大自诩和。dp[i]的计算有两种情况:

1.dp[i]之包括一个元素,就是a[i];

2.dp[i]包括多个元素,从前面某个a[v]开始,v<i,到a[i]结束,即dp[i-1]+a[i]。去两者最大值

 

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int dp[10005];
  4. int main()
  5. {
  6. int t; cin>>t;
  7. for(int k=1;k<=t;k++)
  8. {
  9. int n;cin>>n;
  10. for(int i=1;i<=n;i++)cin>>dp[i];
  11. int start=1,end=1,p=1;
  12. int maxsum=dp[1];
  13. for(int i=2;i<=n;i++)
  14. {
  15. if(dp[i-1]+dp[i]>=dp[i])
  16. dp[i]=dp[i-1]+dp[i];
  17. else
  18. p=i;
  19. if(dp[i>maxsum])
  20. {
  21. maxsum=dp[i];start=p;end=i;
  22. }
  23. }
  24. printf("Case %d:\n",k);printf("%d %d %d\n",maxsum,start,end);
  25. if(k!=t) cout<<endl;
  26. }
  27. return 0;
  28. }

 问题(2)的思路:

首先求前缀和s[i],s[I]是a[1]到a[i]的和。之后问题转化为找出两个位置i,k,使s[i]-s[k]最大,并且I-k<=m.如果暴力的去检查的话复杂度是O(mn),会超时。我们不妨用一个长度为m的滑动窗口即单调队列来维护。代码如下

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. deque<int>dq;
  4. int s[100005];
  5. int main()
  6. {
  7. int n,m; scanf("%d%d",&n,&m);
  8. for(int i=1;i<=n;i++) scanf("%d",&s[i]);
  9. for(int i=1;i<=n;i++) s[i]=s[i-1]+s[i];
  10. int ans=-1e8;
  11. dq.push_back(0);
  12. for(int i=1;i<=n;i++)
  13. {
  14. while(!dq.empty()&&dq.front()<i-m) dq.pop_front();
  15. if(dq.empty()) ans=max(ans,s[i]);
  16. else ans=max(ans,s[i]-s[dq.front()]);
  17. while(!dq.empty()&&s[dq.back()]>=s[i]) dq.pop_back();
  18. dq.push_back(i);
  19. }
  20. printf("%d",ans);
  21. return 0;
  22. }

优先队列会结合堆来讲。 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/975256
推荐阅读
相关标签
  

闽ICP备14008679号