文章目錄
一、隊列的概念
這就是現實生活中的隊列。
隊列是一種特殊的線性表,其插入操作限定在表的一端進行,稱爲“入隊”;其刪除操作則限定在表的另一端進行,稱爲“出隊”。插入一端稱爲隊尾,刪除一端稱爲隊首。因此,隊列也被稱作“先進先出”的線性表(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~