哈利波特魔法覺醒工作室就女性巫師模型問題發致歉信,解釋 Bug 產生原因
目錄
一、定義
二、構造
1、具象形狀
2、建佇列
3、入隊
4、出隊
5、檢視隊首元素
6、判斷佇列是否為空
7、佇列的大小
三、結尾
定義
-
一種資料結構
-
一種特殊的線性表
-
遵循先進先出\((First\;In\;Last\;Out)\)原則
構造
具象形狀
日常生活中,有序的排隊是常有的事,比如說學生黨排隊打飯之類的。
並且顯而易見的,先排隊的人先打到飯。(應該不會有人插隊吧)
所以我們這群優雅人士,給排隊乾飯之類的一系列事情統一了一個結構,佇列。
建佇列
我們既然要給資料排隊,那麼我們就必須要給資料一個排隊的視窗,對吧?
所以我們使用佇列的第一步,就是給佇列一個頭。
- STL版
我們需要先引入queue這個為我們封裝好佇列的基本函式的庫,然後建立佇列。
#include <queue>
using namespace std;
queue</*資料型別*/> /*變數名*/;
// 比如:
#include <queue>
using namespace std;
queue<int> Q;
但是,由於我不喜歡用
using namespace std;
所以,我一般這麼寫。
#include <queue> std::queue<int> Q;
- 手打版
首先,要明確的一點就是我們手打佇列需要指明佇列的頭和尾,所以我們需要定義頭和尾的指標與佇列的大小。
/*資料型別*/ /*變數名*/[/*陣列大小*/];
/*資料型別*/ /*作為頭指標的變數名*/;
/*資料型別*/ /*作為尾指標的變數名*/;
// 比如:
int Q[100001]; // 佇列存放資料的大小
int head = 1; // 佇列的頭指標
int tail = 0; // 佇列的尾指標
這裡我們定義了尾指標的大小為0,而頭指標的大小為1,是為什麼呢?
我們可以想象一下,什麼時候頭指標會在尾指標後面呢?
很顯然就是佇列裡面沒有元素,所以尾指標會閒得發慌,跑到頭指標的前面(出頭鳥),對吧?
入隊
我們以上面乾飯的學生為例。
如果,你也要當個乾飯的學生(我說的是如果),那麼就必須要先排隊打飯才能幹對不對?
同樣的如果我們要讓資料們有次序的被利用,那麼我們就需要先讓資料排好隊。
而排好隊的前提就是讓資料去排隊,所以就有了我們的入隊操作。
- STL版
運用queue庫裡面的push()函式,就可以輕易的將資料入隊了。
我們以上面開的int型別的數列來演示。
int a;
Q.push(a);
- 手打版
很明顯的,當我們需要將資料排進佇列裡面,而佇列還沒有出去一個人的時候,隊伍的尾巴會變長。
void push(int a) {
++tail;
Q[tail] = a;
}
出隊
當我們打完飯就要去幹飯的對不對,那我們不可能賴在隊伍裡面吧?
所以我們要離開隊伍。
這個資料的離隊叫做出隊操作。
- STL版
queue庫裡面提供了一個出隊函式,叫做pop()。
Q.pop();
- 手打版
我們知道,佇列的原則是\(FIFO\),所以我們每次出隊的時候出隊的都是隊首。
void pop() {
++head;
}
我們將頭指標往後移動了一個,隊首變為原來的下一個,相當於原來那個出了佇列,這樣一來就不用去維護頭指標永遠是1的位置,就相當於用少量的空間來取代大量的時間。
因為如果我們維護頭指標永遠為1的話,每次出隊都將用\(O(n)\)的時間來將整個用陣列維護的佇列前移,會導致程式執行時間超過題目的限制。
檢視隊首元素
有的學校排隊打飯時用的是餐卡(至少我們學校是),用餐卡就會記錄你的消費資訊。
同樣的,有時候我們也需要記錄排過隊的資料的資訊,所以我們每次在資料出隊的時候要檢視資料幹了什麼,這個操作可以通過我們先檢視隊首元素並記錄,然後再將隊首元素出隊。
因此我們就需要檢視隊首元素這一操作。
- STL版
同樣,queue庫給我們提供了檢視隊首元素的函式,front()。
int a;
a = Q.front();
- 手打版
還記得我們前面定義的\(head\)指標嗎?
這時候我們要用到它了。
因為我們定義了\(head\)指標來作為佇列的頭指標,所以?所以我們可以用\(head\)來返回隊首的元素。
int front() {
return Q[head];
}
int main (void) {
...balabala...
int a = front();
...balabala...
}
這樣子就可以取到隊首的元素了。
判斷佇列是否為空
雖然我們日常生活中的排隊很容易就可以看出來還有沒有人在排隊,但是我們在程式中不能確定這個時刻我們這個佇列中還有沒有資料,所以我們需要判斷佇列是否為空。
- STL版
要判斷佇列是否為空,queue庫給我們提供了一個函式,empty()。
Q.empty();
- 手打版
對於我們手打的佇列,要判斷佇列是否為空,也是很簡單的。
首先,我們可以回想一下,我們一開始定義的\(head\)指標和\(tail\)指標。
我們一開始將\(head\)指標賦值為\(1\),而\(tail\)指標賦值為\(0\)。
這意味著什麼,意味著隊首在隊尾的後面。
意味著這時候佇列裡面沒有任何的元素。
所以我們判斷佇列是否為空,就等於在判斷隊首有沒有在隊尾的後面。
bool empty() {
return head > tail; // 當佇列為空,即head > tail的時候,empty()作為 檢查佇列是空的 的函式,返回true;相反的,當佇列不為空,即head ≤ tail的時候,返回false。
}
佇列的大小
我們也需要檢視佇列有多少元素,所以我們有時候需要有檢視佇列大小這個操作。
- STL版
檢視佇列大小,在queue庫裡叫size()。
Q.size();
- 手打版
好傢伙,我們又要用到\(head\)和\(tail\)了。
我們一開始\(head = 1, tail = 0\)的賦值方法告訴了我們當佇列裡面有至少一個元素的時候,\(head ≤ tail\)。
也就是說,當我們佇列裡面有元素的時候,會滿足\(tail - head ≥ 0\)。
並且,當元素是\(1\)的時候,\(tail - head = 0\)。
所以我們將等式的兩邊同時加上\(1\),可以有\(tail - head + 1 = 1\)。
同樣的一開始的式子就可以變成\(tail - head + 1 ≥ 1\)。
也就是對於我們手打的佇列,隊伍中的元素個數是\(tail - head + 1\)個。
int size() {
return (tail - head + 1);
}
結尾
用STL寫的佇列 與 用手打的佇列 各有千秋,一般來說,沒有吸氧(開\(O2\)優化)的話,用手打的佇列比用STL寫的佇列來的快;吸氧(開\(O2\)優化)的話,兩者時間差不多。
而且單說打比賽的話,從CSP到NOIP,是不可以開O2優化(2021年開啟了吸氧時代,所以當我沒說)的,所以總體上是手打佇列快。
所以我選擇STL!!!!!
因為STL打的快啊!!!!!(懶人必備神器)
部落格園:https://www.cnblogs.com/Juro/
Copyright ©2021 Juro
【轉載文章務必保留出處和署名,謝謝!】