【常用数据结构——队列&&栈】(最喜欢STL自带的东西了)
队列(queue)
简介
队列,就像他的名字一样,排队的人从后面接上,前面的人办完事情就离开。
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO-first in first out)线性表。
而队列通常也分为两种类型:
顺序队列
建立这种队列必须为其静态分配或者动态申请一段空间,并用队头指针(front)和队尾指针(rear)进行管理,当插入一个数时,队头指针加1,弹出一个数时,队尾指针加1,当队头指针等于队尾指针,则是一个空队列,当队头指针超出分配的空间之外时,就无法插入。这种队列的缺点就是用过一次的空间不会再次运用,大大浪费空间。
循环队列
所以为了让空间重复利用,当我们对这个队列进行插入或者删除的操作时,只要超出了我们分配的空间,就让指针指向这片空间的起始位置,用取余操作实现,但是注意空间要开的足够大,否则会出现队头指针追上队尾指针的情况。
基本操作(STL)
size() | 获取队列长度 |
push() | 插入一个数,进行入队操作(队头) |
pop() | 删除一个数,进行出对操作(队尾) |
front() | 获取队头元素 |
empty() | 判断队列是否为空,是就返回1 |
不说了,我爱STL
实际运用
我们经常在BFS中用到队列(大家都知道吧)然后........我们就上一篇最蒟蒻的约瑟夫问题让大家康康吧(话说这真是个万能的题呢)
首先,我们来看看用STL中的queue的方法做的吧
1 #include<bits/stdc++.h> 2 using namespace std; 3 queue<int>q; 4 int n,out,now=1; 5 int main() 6 { 7 scanf("%d%d",&n,&out); 8 for (int i=1;i<=n;i++) 9 q.push(i);//初始化 10 while (!q.empty())//队列不为空 11 { 12 if(now==out) 13 { 14 cout<<q.front()<<" ";//打印编号 15 q.pop();//出局 16 now=1;//初始化 17 } 18 else 19 { 20 now++; 21 q.push(q.front());//排到队尾 22 q.pop(); 23 } 24 } 25 cout<<endl; 26 return 0; 27 }
至于手写的.....我有点懒,以后补充吧
优先队列
简介
普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。(不要管堆是什么玩意儿,安心用你的STL就行了)
基本操作(STL)
定义
定义优先队列时,默认从大到小的顺序出队,我们也可以用重载运算符自定义优先级
priority_queue<int> q; //通过操作,按照元素从大到小的顺序出队
priority_queue<int,vector<int>, greater<int> > q; //通过操作,按照元素从小到大的顺序出队
然后写一段程序看看肿么样
1 #include<bits/stdc++.h> 2 using namespace std; 3 priority_queue<int,vector<int>, greater<int> > q; 4 int main() 5 { 6 q.push(10),q.push(5),q.push(15),q.push(2),q.push(7); 7 for(int i=1;i<=5;i++) 8 { 9 cout<<q.top()<<endl; 10 q.pop(); 11 } 12 return 0; 13 }
2 5 7 10 15
size() | 获取队列长度 |
empty() | 判断队列是否为空 |
push() | 在队列末尾插入一个数 |
pop() | 删除队首元素 |
top() | 获取队首元素 |
实际应用
最常见的就是用在拓扑排序和优化最短路中的dijkstra.........所以,排队,又是一道例题。
Description 今天,学校老师让同学们排成一队,准备带大家出去玩,一共有 n 名同学, 排队的时候同学们向老师提了 m 条要求, 每一条要求是说同学 x 一定要排在同学 y 之前, 老师现在想找到一种排队方式可以满足所有的要求,你能帮帮他吗? Input 第一行两个整数 n,m(1≤n≤10410^4104,1≤m≤10510^5105),表示同学数和要求数; 以下 m 行,每行两个整数 x,y,之间用一个空格隔开, 表示某个要求是第 x 号同学要排在第 y 号同学之前,所有同学的编号由 1 开始; 输入保证有解。 Output 输出一行,包括 n 个整数,表示从前往后同学的编号,用空格隔开,如果答案不唯一,输出字典序最小的答案。 Sample Input 1 2 1 1 2 Sample Output 1 1 2 Hint 提示:使用优先队列进行拓扑排序
1 #include<bits/stdc++.h> 2 using namespace std; 3 priority_queue<int,vector<int>,greater<int> >q;//字典序小的在前面 4 vector<int> lt[10010]; 5 vector<int> ans; 6 int in[10010]; 7 int n,m; 8 int main() 9 { 10 cin>>n>>m; 11 while(m--) 12 { 13 int a,b; 14 cin>>a>>b; 15 in[b]++;//入度(限制数) 16 lt[a].push_back(b);//存图 17 } 18 for(int i=1;i<=n;i++) 19 { 20 if(in[i]==0)//没有限制就随便排 21 q.push(i); 22 } 23 while(!q.empty()) 24 { 25 int sum=q.top(); 26 q.pop(); 27 ans.push_back(sum); 28 for(int j=0;j<lt[sum].size();j++) 29 { 30 in[lt[sum][j]]--; 31 if(in[lt[sum][j]]==0) 32 q.push(lt[sum][j]); 33 } 34 } 35 for(int i=0;i<ans.size();i++) 36 cout<<ans[i]<<" "; 37 return 0; 38 }
单调队列
简介
单调队列,即单调递减或单调递增的队列。使用频率不高,但在有些程序中会有非同寻常的作用。
他有两个特点
1,队列中的元素其对应在原来的列表中的顺序必须是单调递增的。
2,队列中元素的大小必须是单调递(增/减/甚至是自定义也可以)
他的玄学在于可以队首出队,也可以队尾出队(????)
然后一般用于求区间最值问题(RMQ)
基本操作
front() | 返回队首元素 |
back() | 返回队尾元素 |
pop_back() | 删除队尾元素 |
pop_front() | 删除队首元素 |
push_back() | 插入队尾元素 |
push_front() | 插入队首元素 |
实际运用
我们一般采用双端队列(deque)(STL大法好!!!,当然是方便啦)当然,你也可以手写我们这里全程拿滑动窗口作为例题吧。
为什么它可以用来求RMQ呢?别急,那样例做解释:
我们拿求这一段的最小值作为说明
我们用q表示队列,p表示对应下标
首先你有这样一串数,然后输出连续每三个中的最小值
因为队列没有元素,所以第一个果断进去
q={1}
p={1}
然后我们看向3,设想如果后面两个数都比它大,那它就还有机会啊是不是(不像我已经没有机会了)
q={1,3}
p={1,2}
然后就是-1,因为它比3小,只要-1进了队,在后面中,有3的每一个区间肯定有-1,所以3完全就是多余的,没必要再留着了,1同理
q={-1}
p={3}
-3同理
q={-3}
p={4}
5来了,因为大于-3,并且还有机会所以可以加进去
q={-3,5}
p={4,5}
3来了,因为小于5,所以5没机会了,但是-3还有机会
q={-3,3}
p={4,6}
6,因为6>3,所以进去,但是-3已经没在窗口内了,所以踢出
q={3,6}
p={6,7}
7来了,同理
q={3,6,7}
p={6,7,8}
因为队列是单调递(增/减/自定义),所以输出队首就行了(肯定是最优的)
这一段我用的STL
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,k,x; 4 struct node{//编号和值 5 int num; 6 int val; 7 }; 8 deque<node> q_max;//求最大值 9 deque<node> q_min;//求最小值 10 int ans[2][1000005];//每一段的答案 11 int cnt; 12 int main() 13 { 14 scanf("%d%d",&n,&k); 15 for(int i=1;i<=n;i++) 16 { 17 scanf("%d",&x); 18 node head; 19 head.num=i; 20 head.val=x; 21 //最大 22 while(!q_max.empty()&&x>=q_max.back().val)//不为空并且比队尾大,证明队尾的那些以后没机会最大了了 23 q_max.pop_back(); 24 q_max.push_back(head);//插入 25 while(i-k>=q_max.front().num)//判断队首是否在窗内,不是就删掉 26 q_max.pop_front(); 27 //最小 ,同上 28 while(!q_min.empty()&&x<=q_min.back().val) 29 q_min.pop_back(); 30 q_min.push_back(head); 31 while(i-k>=q_min.front().num) 32 q_min.pop_front(); 33 34 if(i>=k) 35 { 36 cnt++;//记录,队首肯定是最优的 37 ans[0][cnt]=q_max.front().val; 38 ans[1][cnt]=q_min.front().val; 39 } 40 } 41 //输出 42 for(int i=1;i<cnt;i++) 43 cout<<ans[1][i]<<" "; 44 cout<<ans[1][cnt]<<endl; 45 for(int i=1;i<cnt;i++) 46 cout<<ans[0][i]<<" "; 47 cout<<ans[0][cnt]<<endl; 48 }
下面是手写的(怎么感觉简单一些)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,k; 4 int a[1000005]; 5 int q[1000005]; 6 void deque_max() 7 { 8 int h=1,t=0;//头指针,尾指针 9 for(int i=1;i<=n;i++) 10 { 11 while(h<=t&&q[h]+k<=i)h++;//超出窗口就弹出 12 while(h<=t&&a[i]<=a[q[t]])t--;//比队尾大,队尾的没机会了也弹出 13 q[++t]=i;//入队 14 if(i>=k)printf("%d ",a[q[h]]);//输出 15 } 16 cout<<endl; 17 } 18 void deque_min()//同上 19 { 20 int h=1,t=0; 21 for(int i=1;i<=n;i++) 22 { 23 while(h<=t&&q[h]+k<=i)h++; 24 while(h<=t&&a[i]>=a[q[t]])t--; 25 q[++t]=i; 26 if(i>=k)printf("%d ",a[q[h]]); 27 } 28 cout<<endl; 29 } 30 int main() 31 { 32 scanf("%d%d",&n,&k); 33 for(int i=1;i<=n;i++) 34 scanf("%d",&a[i]); 35 deque_max(); 36 memset(q,0,sizeof(q));//清空队列 37 deque_min(); 38 }
栈(stack)
简介
基本操作(STL)
size() | 获取栈的深度 |
top() | 获取栈顶元素 |
pop() | 弹出栈顶元素 |
push() | 向栈顶插入元素 |
empty() | 判断栈是否为空 |
单调栈
简介
单调递增或单调减的栈,跟单调队列差不多,但是只用到它的一端,利用它可以用来解决一些ACM/ICPC和OI的题目,如RQNOJ 的诺诺的队列等。
基本操作
因为单调栈还是只用它的一段,所以和普通的栈没什么明显的区别。同样的,可以手写也可以用STL自带的
实践运用
直接单调递减的的栈,还是拿样例过一遍(自己编的不要在意)
2进来
s={2}
p={1}
6进来,因为6>2,所以2不可能成为后面的数左边第一个比自己的大的数,所以可以踢掉,然后2的右边最大的就是6
s={6}
p={2}
8进来,同理,然后6右边第一个比自己大的就是8
s={8}
p={3}
1进来,万一遇到比一小的还有希望,然后8就是1右边最大的
s={8,1}
p={3,4}
5进来,同理,1出去,5是1右边大的,8是5右边大的
s={8,5}
p={3,5}
然后康康代码
1 #include<bits/stdc++.h> 2 #define MAX 1000005 3 using namespace std; 4 int n,top,ans; 5 int h[MAX],v[MAX],sum[MAX]; 6 int s[MAX]; 7 int main() 8 { 9 scanf("%d",&n); 10 for(int i=1;i<=n;i++) 11 { 12 scanf("%lld%d",&h[i],&v[i]);//高度 和能量 13 while(top&&h[s[top]]<h[i])sum[i]+=v[s[top--]];//遇到小的就退出,然后小的右边第一个大的就是它,加上去 14 sum[s[top]]+=v[i];//然后栈顶比自己大,肯定是左边第一个比自己大的 15 s[++top]=i;//进栈 16 } 17 for(int i=1;i<=n;i++) 18 ans=max(ans,sum[i]);//循环过一遍 19 cout<<ans; 20 } 21
好啦,希望这篇博客对你们理解栈和队列有更好的帮助!