【佇列】佇列 Queue(一):順序佇列與迴圈佇列
背景
沒什麼背景,就是想研究下佇列。
說明
什麼是佇列(Queue)?
佇列在生活中可謂是無處不在。最常見的就是去超市買菜稱重時大媽們排得賊長的佇列(這是理想情況,通常是圍成一圈),還有超市結賬的隊伍,還有以前食堂打飯的隊伍。是不是很有印象呢~~~
那佇列有什麼特點呢?
就拿食堂打飯來說,下課鈴聲一響,千萬大軍衝向食堂,為的是早來早打上飯,晚來了,那隊伍忒長了,想死的心都有了~~~為什麼會這樣子呢?佇列有個原則叫做先進先出。先來排隊的人先打飯,後來的人後打飯。比如說,佇列 A 有 3 個人:A1,A2,A3。A1 是排第 1 個,A2 是排第 2 個,A3 是排第 3 個。那麼,先打上飯的是 A1; A1 打完飯後,退出佇列,到 A2 打飯;A2 打完飯後,退出佇列,到 A3 打飯。如果在 A1,A2,A3 打飯期間,沒有人來排隊,等到 A3 打完飯後,佇列就空了,我們稱沒有元素的佇列為空隊
前面對打飯大軍的介紹,我們對隊伍有了初步的認識。下面將要用專業地描述佇列。
佇列的定義
佇列(Queue),簡稱:隊,是一種只允許在表的一端進行插入操作,而在表的另外一端進行刪除操作的線性表。
關於佇列的基本概念:
- 隊尾(rear):允許進行插入的一端
- 隊頭(front):允許進行刪除的一端
- 進隊:佇列的插入操作
- 出隊:佇列的刪除操作
- 原則:先進先出(First In First Out,FIFO)
佇列的基本操作
- 佇列的初始化。
- 進隊:在佇列的尾部插入一個新的元素。Ps:進隊將改變隊尾指標的位置。
- 出隊
- 測試佇列是否為空。Ps:在佇列刪除操作前必須進行本操作。
- 測試佇列是否已滿。Ps:本操作通常只是在佇列採用順序儲存結構時對佇列插入操作之前要進行的一個操作。
- 取當前隊頭的元素。Ps:本操作不修改隊頭元素指標的位置。
佇列的儲存結構
一般來說,佇列有兩種儲存結構:順序儲存結構和鏈式儲存結構。
順序佇列
我們把採用順序儲存結構的佇列簡稱為:順序佇列。
在實際的程式設計中,通常使用陣列來描述佇列的順序儲存結構:
- 定義一維陣列 QUEUE[0…M-1] 來存放佇列的元素。
- 定義整型變數 front 指出隊頭元素的位置。
- 定義整型變數 rear 指出隊尾元素的位置。
NOTE
為了演算法設計的方便以及演算法本身的簡單,我們約定:
- 隊頭指標 front 指出實際隊頭元素所在位置的前一個位置。
- 隊尾指標 rear 指出實際隊尾元素所在的位置。
下面,咱們以圖例的方式介紹佇列的變化過程。
1.順序佇列的定義
#define QUEUE_LENGTH 1000 /*定義佇列的最大容量*/
QElemType Queue[QUEUE_LENGTH];
int front,rear;
PS:
ElemType 就是“資料元素的型別”,是一個抽象的概念,是表示我們所要使用的資料元素應有的型別。
2.順序佇列的初始狀態
初始化時,佇列為空,有 front = rear = -1。
3.元素 a1 進隊後的狀態
元素 a1 從隊尾進隊,rear = 0。
4.元素 a2 進隊後的狀態
元素 a2 從隊尾進隊,rear = 1。
5.元素 a1 出隊後的狀態
元素 a1 從隊頭出隊,front = 0。
6.元素 a3 進隊後的狀態
元素 a3 從隊尾進隊,rear = 2。
7.元素 a2 出隊後的狀態
元素 a2 從隊頭出隊,front = 1。
8.元素 a3 出隊後的狀態
元素 a3 從隊頭出隊, front = 2。此時,佇列已無元素,佇列為空。
NOTE
在佇列的設計過程中,我們需要考慮如下的異常情況。
- 非空佇列隨著刪除操作的進行,佇列可能為空。由上面的例程可知,佇列為空的條件為 front = rear。
- 順序佇列有可能出現溢位問題。當佇列已滿時進行進隊操作,這種現象稱為:上溢。當佇列已空時進行出隊操作,這種現象稱為:下溢。
順序佇列的基本演算法
- 佇列的初始化
- 測試佇列是否為空
- 測試佇列是否為滿
- 取當前隊頭的元素
- 佇列的插入(進隊)
- 佇列的刪除(出隊)
佇列的初始化
/*
Function: Init Quueue
Intput:
Queue, ptr to Queue
Return: None
*/
void Queue_Init(QueueType *Queue)
{
memset(Queue->Queue, 0, sizeof(Queue->Queue)/sizeof(QElemType));
Queue->front = -1;
Queue->rear = -1;
}
測試佇列是否為空
/*
Function: Empty Queue?
Intput:
Queue, ptr to Queue
Return:
Queue_EMPTY
Queue_OK
*/
static Queue_Status Queue_IsEmpty(QueueType *Queue)
{
if (Queue->front == Queue->rear)
{
QueuePrintf("Err: Queue is empty!\r\n");
return Queue_EMPTY;
}
else
{
return Queue_OK;
}
}
測試佇列是否為滿
/*
Function: Full Queue?
Intput:
Queue, ptr to Queue
Return:
Queue_FULL
Queue_OK
*/
static Queue_Status Queue_IsFull(QueueType *Queue)
{
if (Queue->rear == (QueueLength - 1))
{
QueuePrintf("Err: Queue is full!\r\n");
return Queue_FULL;
}
else
{
return Queue_OK;
}
}
取當前隊頭的元素
/*
Function: get element from queue
Intput:
Queue, ptr to Queue
item
Return:
Queue_EMPTY
Queue_OK
Other:
Don't change the front of Queue
*/
int Queue_GetElement(QueueType *Queue, QElemType &item)
{
// If Queue is empty, return Queue_EMPTY
if (Queue_EMPTY == Queue_IsEmpty(Queue))
{
return Queue_EMPTY;
}
else
{ // else get the item from Queue
item = Queue->Queue[Queue->front];
return Queue_OK;
}
}
佇列的插入(進隊)
/*
Function: add element into queue
Intput:
Queue, ptr to Queue
item
Return:
Queue_FULL
Queue_OK
*/
int Queue_AddElement(QueueType *Queue, QElemType &item)
{
// If Queue is full, return Queue_FULL
if (Queue_FULL == Queue_IsFull(Queue))
{
return Queue_FULL;
}
else
{ // else add the item into Queue
Queue->Queue[++Queue->rear] = item;
return Queue_OK;
}
}
佇列的刪除(出隊)
/*
Function: delete element into queue
Intput:
Queue, ptr to Queue
item
Return:
Queue_EMPTY
Queue_OK
*/
int Queue_DeleteElement(QueueType *Queue, QElemType &item)
{
// If Queue is empty, return Queue_EMPTY
if (Queue_EMPTY == Queue_IsEmpty(Queue))
{
return Queue_EMPTY;
}
else
{ // else delete the item into Queue
item = Queue->Queue[++Queue->front];
return Queue_OK;
}
}
順序佇列的弊端
由程式碼和圖例可以看出,順序佇列的 Queue_AddElement 中,當 rear隨著入隊累加,有可能出現 rear = QueueLength -1,導致在此時進行插入操作會返回溢位錯誤,佇列的動態變化在向右偏移,我們把這種溢位稱為“假溢位”。這並不是我們所期望的。由此引申出“迴圈佇列”的概念,下面看怎麼通過迴圈佇列解決順序佇列的問題。
迴圈佇列
如果把佇列設想成頭尾相連的迴圈表,使得空間可以迴圈利用,問題迎刃而解。
在進行插入操作時,當佇列的第 M - 1 個元素被佔用以後,只要佇列前面還有可用空間,新的元素就可以從第 0 個位置開始加入佇列。那麼入隊函式可以改為:
/*
Function: add element into queue
Intput:
Queue, ptr to Queue
item
Return:
Queue_FULL
Queue_OK
*/
int Queue_AddElement(QueueType *Queue, QElemType &item)
{
// If Queue is full, return Queue_FULL
if (Queue_FULL == Queue_IsFull(Queue))
{
return Queue_FULL;
}
else
{ // else add the item into Queue
Queue->rear = (Queue->rear + 1) % QueueLength;
Queue->Queue[Queue->rear] = item;
return Queue_OK;
}
}
出隊函式改為:
/*
Function: delete element into queue
Intput:
Queue, ptr to Queue
item
Return:
Queue_EMPTY
Queue_OK
*/
int Queue_DeleteElement(QueueType *Queue, QElemType &item)
{
// If Queue is empty, return Queue_EMPTY
if (Queue_EMPTY == Queue_IsEmpty(Queue))
{
return Queue_EMPTY;
}
else
{ // else delete the item into Queue
Queue->front = (Queue->front + 1) % QueueLength;
item = Queue->Queue[Queue->front];
return Queue_OK;
}
}
而且,對於佇列是否為 Empty 和 Full 的標準也隨之改變:
/*
Function: Full Queue?
Intput:
Queue, ptr to Queue
Return:
Queue_FULL
Queue_OK
*/
static Queue_Status Queue_IsFull(QueueType *Queue)
{
if (((Queue->rear + 1)%QueueLength) == Queue->front)
{
QueuePrintf("Err: Queue is full!\r\n");
return Queue_FULL;
}
else
{
return Queue_OK;
}
}
/*
Function: Empty Queue?
Intput:
Queue, ptr to Queue
Return:
Queue_EMPTY
Queue_OK
*/
static Queue_Status Queue_IsEmpty(QueueType *Queue)
{
if (Queue->front == Queue->rear)
{
QueuePrintf("Err: Queue is empty!\r\n");
return Queue_EMPTY;
}
else
{
return Queue_OK;
}
}