## 定義
通常討論時會唸英文,但硬要說中文就是佇列
在線性資料結構中滿足 FIFO(First In First Out),也就是先進先出(亦後進後出)
如果用比喻來說,最常見的例子就是火車過山洞,車頭先進山洞也先出,車尾後進山洞也後出
佇列通常會使用幾個基本的功能:push()、pop()、front()、empty()、size()
* push() : 將資料推入 queue 的最後端
* pop() : 將 queue 最前端的資料取出(刪除)
* front() : 回傳 queue 最前端的資料
* empty() : 回傳 queue 是否為空
* size() : 回傳 queue 的大小
## 實作
通常會有多個方法實作,不過競程的大部分題目只需要用到 STL 內建的 queue
實際競賽上不太會需要自己搓一個,所以這裡的做法參考就好,知道怎麼做就可以了
除非你要考 APCS 的觀念題,那這裡的陣列實作建議看一下,考的機率滿高的
以下是陣列的做法,用比較偷懶的方式實作的
```cpp=
#include<bits/stdc++.h>
using namespace std ;
int Q[100] ; // queue 大小最多為 100
int f = 0, r = 0 ; // f 目前最前端的位置, r 目前最尾端的位置
void push(int data) {
if ((r+1)%100 != f) {
Q[r] = data ;
r = (r+1) % 100 ;
}
else
cout << "The queue is already full." ; // 裝滿的 queue 沒辦法 push
return ;
}
void pop() {
if (f != r) {
Q[f] = 0 ;
f = (f+1) % 100 ;
}
else
cout << "The queue is already empty." ; // 空 queue 沒辦法 pop
return ;
}
int bfront() {
if (f != r)
return Q[f] ;
cout << "The queue is already empty." ; // 空 queue 沒辦法 front
return -1 ; // 回傳 -1 表示 error
}
bool bempty() { // empty() 為內建函式,故取他名迴避
return f == r ;
}
int bsize() { // size() 為內建函式,故取他名迴避
return (r-f+100)%100 ;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0) ;
int n, data ;
cin >> n ; // 有 n 筆資料需要傳入 queue
for (int i=0;i<n;i++) {
cin >> data ;
push(data) ;
}
while (!bempty()) { // 取出並輸出 queue 資料直到 queue 為空
data = bfront() ;
pop() ;
cout << data << ' ' ;
}
return 0 ;
}
```
---
以下是 Linked List 實作的方式
```cpp=
#include<bits/stdc++.h>
using namespace std ;
struct Node { // Linked List 的結構
int data ;
Node* next ;
Node* prev ;
} ;
Node* push(Node* curr, int data) {
Node* new_node = new Node ;
new_node->data = data ;
new_node->next = curr ;
new_node->prev = curr->prev ;
curr->prev = new_node ;
return new_node ;
}
Node* pop(Node* curr) {
if (curr->data != -1) { // 不是 head
Node* now = curr ;
now->next->prev = now->prev ;
now->prev->next = now->next ;
curr = curr->prev ;
delete now ;
}
else
cout << "The queue is already empty." ; // 空 queue 沒辦法 pop
return curr ;
}
int bfront(Node* curr) {
if (curr->data != -1) { // 不是 head
return curr->data ;
}
cout << "The queue is already empty." ; // 空 queue 沒辦法 front
return -1 ; // 回傳 -1 表示 error
}
bool bempty(Node* curr) { // empty() 為內建函式,故取他名迴避
return curr->data == -1 ;
}
int bsize(Node* curr) { // size() 為內建函式,故取他名迴避
int len = 0 ;
while (curr->data != -1)
curr = curr->next, len++ ;
return len ;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0) ;
// 初始化
Node *head = new Node ;
head->data = -1 ;
head->prev = head->next = head ;
// 最尾端
Node *curr = head ;
int n, data ;
cin >> n ; // 有 n 筆資料需要傳入 queue
for (int i=0;i<n;i++) {
cin >> data ;
curr = push(curr, data) ;
}
curr = head->prev ;
while (!bempty(curr)) { // 取出並輸出 queue 資料直到 queue 為空
data = bfront(curr) ;
curr = pop(curr) ;
cout << data << ' ' ;
}
return 0 ;
}
```
## STL queue 使用方法
其實最主要的還是競賽時的使用,通常都是使用 STL 內建的 queue 來操作,以下是實際用法範例
```cpp=
#include<bits/stdc++.h>
using namespace std ;
int main() {
ios::sync_with_stdio(0), cin.tie(0) ;
queue<int> que ; // 宣告一個儲存 int 的 queue
int n, data ;
cin >> n ;
for (int i=0;i<n;i++) {
cin >> data ;
que.push(data) ; // push data 進 queue
}
while (!que.empty()) { // 同 while (que.size() > 0)
cout << que.front() << ' ' ; // 輸出 top
que.pop() ; // 取出
}
return 0 ;
}
```
## 例題-ZJ e447. queue 練習
[題目連結](https://zerojudge.tw/ShowProblem?problemid=e447)
解題思路 :
主要是幫助各位練習 STL 裡面的 queue 怎麼使用
```cpp=
#include<bits/stdc++.h>
using namespace std ;
int main() {
ios::sync_with_stdio(0), cin.tie(0) ;
int n ;
cin >> n ;
queue<int> que ;
for (int i=0, k;i<n;i++) {
cin >> k ;
if (k == 1) { // 推入 k
cin >> k ;
que.push(k) ;
}
else if (k == 2) {
if (que.empty()) // 沒元素輸出 -1
cout << "-1\n" ;
else // 輸出最頂端
cout << que.front() << '\n' ;
}
else {
if (!que.empty()) // 有元素才可刪除
que.pop() ;
}
}
return 0 ;
}
```
## 例題-ZJ e155. 10935 - Throwing cards away I
[題目連結](https://zerojudge.tw/ShowProblem?problemid=e155)
解題思路 :
題目說只要兩張牌以上,就要丟掉最上方的牌,次高的牌放到牌堆最下方,直接模擬出來就好
```cpp=
#include<bits/stdc++.h>
using namespace std ;
int main() {
ios::sync_with_stdio(0), cin.tie(0) ;
int n ;
while (cin >> n && n) {
cout << "Discarded cards: " ;
queue<int> que ;
for (int i=1;i<=n;i++)
que.push(i) ;
while (que.size() > 1) { // 至少兩張才能開始丟
cout << que.front() ;
que.pop() ; // 最上方的丟掉
que.push(que.front()) ; // 次上方的保留放到最後
que.pop() ;
if (que.size() > 1) // 只要兩張以上就還能丟棄
cout << ", " ;
else break ; // 剩下一張所以無法丟棄
}
cout << "\nRemaining card: " << que.front() << '\n' ;
}
return 0 ;
}
```
## 例題-ZJ e564. 00540 - Team Queue
[題目連結](https://zerojudge.tw/ShowProblem?problemid=e564)
解題思路 :
這題比較複雜,我們先把結構拆開來說,Team Queue 其實就是 queue 裡面放 queue
所以在 push 元素的時候,要判斷有沒有同 team 的成員在 Team queue 裡面
如果有就將該元素放在同 team 的成員後方,其實 team 在 Team queue 裡就是 queue
只要找到有同成員的 queue,就將元素 push 進這個 queue 中
至於 pop 元素的部分,因為整個 team queue 是由很多 queue 組成的
所以會從最前方的 queue 取值,如果最前方的 queue 取完值後是空的,那這個 queue 也要被 pop
不過這種模擬實作起來會有點麻煩,所以我們把 Team queue 拆成兩個部分
1. Team queue 有哪些 team (需要照順序儲存,所以用 queue)
2. 在 Team queue 裡的 team 有哪些成員目前在 Team queue 中
這樣我們在 push 的時候,判斷有沒有同 team 的成員,只要看 2. 就行
pop 的時候也可以先從 1. 知道最前方的 team,然候從 2. 取出值,再判斷 team 成員是不是都跑了
(注意這裡因為有多組測資,所以結構用完後需要清除裡面的資料)
```cpp=
#include<bits/stdc++.h>
using namespace std ;
int t, n, x, k = 0 ;
int id[1000005] ; // id[a] = b, 數字 a 在隊伍 b
queue<int> team[1005] ; // team[a] = {b} 隊伍 a 有 b 在 queue 中
queue<int> que ; // que 不同隊伍在 queue 中的順序
int main() {
ios::sync_with_stdio(0), cin.tie(0) ;
while (cin >> t && t) {
cout << "Scenario #" << ++k << '\n' ;
for (int i=1;i<=t;i++) {
cin >> n ;
for (int j=0;j<n;j++) {
cin >> x ;
id[x] = i ; // 數字 x 在隊伍 b
}
}
string s ;
while (cin >> s && s != "STOP") { // 輸入直到 STOP
if (s == "ENQUEUE") { // push
cin >> x ;
if (team[id[x]].empty()) // 沒有同隊伍的在 queue 中
que.push(id[x]) ; // 放到最後端
team[id[x]].push(x) ; // 放到同隊伍中
}
else {
int now = que.front() ; // 最前方的 team
cout << team[now].front() << '\n' ;
team[now].pop() ;
if (team[now].empty()) que.pop() ; // 全部都出去、queue 已無該隊伍
}
}
cout << '\n' ;
while (!que.empty()) { // 清除資料
int now = que.front() ;
while (!team[now].empty()) team[now].pop() ;
que.pop() ;
}
}
return 0 ;
}
```
## 特殊概念-用兩個 stack 模擬 queue
其實兩個 stack 能夠模擬出 queue 的功能,因為 stack1 將所有資料到另一個 stack2
最上方的元素就是變到最下方,所以原本最先進入 stack1 的資料就可以最先出去,也就是 queue
不過如果要把資料 push 進去,要先把 stack2 的資料先丟回 stack1,不然順序會出問題
```cpp=
#include<bits/stdc++.h>
using namespace std ;
typedef stack<int> STK ;
STK stk1, stk2 ;
void bpush(int data) {
while (!stk2.empty()) {
stk1.push(stk2.top()) ;
stk2.pop() ;
}
stk1.push(data) ;
return ;
}
void btop_and_pop() {
while (!stk1.empty()) {
stk2.push(stk1.top()) ;
stk1.pop() ;
}
cout << stk2.top() << '\n' ;
stk2.pop() ;
return ;
}
int main() {
// 測試資料
bpush(1) ;
bpush(2) ;
bpush(3) ;
btop_and_pop() ;
bpush(4) ;
bpush(5) ;
btop_and_pop() ;
bpush(6) ;
btop_and_pop() ;
btop_and_pop() ;
btop_and_pop() ;
btop_and_pop() ;
btop_and_pop() ;
return 0 ;
}
```
其實還有其他方式,只要可以做出 queue 就可以了
## 總結
之後還有其他演算法會用到 queue,這裡只是拿一些簡單的例題讓各位更了解 queue
唯一比較難的就是 Team queue 那題,因為模擬的複雜度比較高,但其實就是類二維 queue