路過一家奶茶店,由於生意火爆,門口的排著長長的隊伍,先排隊的人先買到奶茶,然後再輪到下一個,秩序井然。有沒有一種資料結構能體現”先來後到“這種順序呢?
當然有,那就是佇列。先看一下定義:佇列是一種操作受限的線性表,它只允許在表的前端(front)進行刪除操作,而在表的後端(rear)進行插入操作。只能在表的最前端刪除,最後端插入,這和排隊買奶茶中先給最前面的人做奶茶,新來的只能在最後面排隊一樣,相對“公平”。
根據定義我們可以知道,佇列主要支援兩種操作,一種是刪除(出隊dequeue)操作,另一種是插入(入隊enqueue)操作,它的一個主要特點是先進先出,這一定要記住
之前我們講過”棧“,它和佇列類似,也是一種操作受限的線性表,但它是”先進後出“,與佇列相反。
佇列的實現
和棧一樣,它可以使用陣列實現(順序佇列),也可以使用連結串列實現(鏈式佇列)。
順序佇列
public class QueueArray<T>
{
//儲存內容的泛型陣列
public T[] items;
//陣列長度
private int len;
//使用頭指標和尾指標輔助入隊、出隊操作
int head, tail = 0;
public QueueArray(int capacity)
{
items = new T[capacity];
len = capacity;
}
//入隊
public bool Enqueue(T val)
{
//尾指標與陣列長度相同,說明佇列已滿,返回false(注意,該判斷存在問題)
if (tail == len)
return false;
items[tail++] = val;
return true;
}
//出隊
public T Dequeue()
{
if (tail == head)
throw new Exception("Queue is empty");
return items[head++];
}
}
現在基本功能已經實現了,但仔細分析會發現,程式碼中的tail
和head
都只會向後移動(數量只會增加),因此tail == len
並不代表陣列已滿,因為陣列頭部的資料可能已經出隊了,前面出現了許多空閒空間。如下圖所示
隨著不停的執行入隊、出隊操作,即使陣列中還有空閒空間,也無法繼續往佇列中新增資料了。此時,需要使用資料搬移,即將佇列中的元素整體搬移到陣列頭,如下圖所示。
該操作只需要在入隊並且”佇列已滿“的時候執行,因此我們需要修改Enqueue()的程式碼為
//入隊
public bool Enqueue(T val)
{
if (tail == len)
{
// tail ==n && head==0,表示整個佇列都佔滿了
if (head == 0) return false;
// 資料搬移
for (int i = head; i < tail; ++i)
{
items[i - head] = items[i];
}
// 搬移完之後重新更新head和tail
tail -= head;
}
items[tail++] = val;
return true;
}
迴圈佇列
迴圈佇列也是基於陣列實現的,並且能夠很好的解決上面當tail==n
需要資料搬移的問題(資料搬移會消耗許多效能)。
顧名思義,環形佇列長得像是一個環,怎麼將陣列“變成”環呢?思路是當tail==n時,如果有空閒位置讓tail = (tail + 1) % len
,即將尾部指標轉移到陣列頭部來開始新的迴圈,這樣修改最關鍵的就是要正確判斷隊空和隊滿的條件。下圖中藍色代表頭指標,紅色代表尾指標
修改入隊和出隊的程式碼
//入隊
public bool Enqueue(T val)
{
//使用尾指標與頭指標來判斷佇列是否滿
if ((tail + 1) % len == head)
return false;
items[tail] = val;
tail = (tail + 1) % len;
return true;
}
//出隊
public T Dequeue()
{
if (tail == head)
throw new Exception("Queue is empty");
T ans = items[head];
head = (head + 1) % len;
return ans;
}
因為判斷隊滿使用的是(tail+1)%n=head
,所以當佇列滿時,tail指向的位置實際上是沒有儲存資料的,浪費了陣列的一個儲存空間。
♂ 程式碼雖然不多,但最好能夠自己手動實現
鏈式佇列
基於連結串列的實現,我們同樣需要兩個指標:head 指標和 tail 指標。它們分別指向連結串列的第一個結點和最後一個結點。如圖所示,入隊時,tail->next= new_node, tail = tail->next
;出隊時,head = head->next
,實現起來比較簡單,這裡就省略了