算法與數據結構(二):隊列
隊列也是一種線性的數據結構,它與鏈表的區別在於鏈表可以在任意位置進行插入刪除操作,而隊列只能在一端進行插入,另一端進行刪除。它對應於現實世界中的排隊模型。隊列有兩種常見的實現方式:基於列表的實現和基於數組的實現
基於鏈表的實現
基於鏈表的隊列,我們需要保存兩個指針,一個指向頭節點,一個指向尾節點。
這種隊列不存在隊列滿的情況,當然也可以根據業務需求給定一個最大值。當頭結點為空時表示隊列為空。由於隊列只能在隊尾插入數據,隊首刪除數據,因此針對隊列的插入需要采用鏈表的尾插法,隊列元素的出隊需要改變頭指針。
隊列節點的定義與鏈表節點的定義相同,下面給出各種操作的簡單實現
typedef struct _QNODE { int nValue; struct _QNODE *pNext; }QNODE, *LPQNODE; extern QNODE* g_front; extern QNODE* g_real;
初始化
初始化操作,我們簡單的對兩個指針進行賦值
bool InitQueue()
{
g_front = g_real = NULL;
return true;
}
元素入隊
元素入隊的操作就是從隊列尾部插入,當隊列為空時,同時也是在隊首插入元素,因此針對元素入隊的操作,需要首先判斷隊列是否為空,為空時,需要針對隊首和隊尾指針進行賦值,而針對隊列不為空的情況下,只需要改變隊尾指針
bool EnQueue(int nVal) { QNODE *p = (QNODE*)malloc(sizeof(QNO if (NULL == p) { return false; } memset(p, 0x00, sizeof(QNODE)); p->nValue = nVal; if (IsQueueEmpty()) //判斷隊列是否為空 { g_front = g_real = p; }else { g_real->pNext = p; g_real = p; } return true; }
判斷隊列是否為空
之前提到過,判斷隊列是否為空,只需要判斷兩個指針是否為空即可,因此這部分的代碼如下:
bool IsQueueEmpty()
{
return (g_front == NULL && g_real == NULL);
}
元素出隊
元素出隊需要從隊首出隊,同樣的需要判斷隊列是否為空。
bool DeQueue(int *pVal) { if(NULL == pVal) { return false; } if (IsQueueEmpty()) { return false; } QNODE *p = g_front; //出隊之後需要判斷隊列是否為空,以便針對隊首、隊尾指針進行賦值 if (NULL == g_front->pNext) { *pVal = g_front->nValue; g_front = NULL; g_real = NULL; }else { *pVal = g_front->nValue; g_front = g_front->pNext; } free(p); return true; }
基於數組的實現
線性結構的隊列也可以使用數組來實現,基於數組的實現也需要兩個指針,分別是指向頭部的下標和指向尾部的下標
基於數組的實現中有這樣一個問題,那就是內存資源的浪費問題。假設現在有一個能存儲10個元素的隊列,當前隊列為空,隨著元素的入隊,此時隊尾指針會一直向後偏移。當裏面有部分元素的時候,來進行元素出隊,這個時候隊首指針會向後偏移。那麽這個時候已經出隊的位置在隊列中再也訪問不到了,但是它所占的內存仍然在那,這樣就造成了內存空間的浪費,隨著隊列的不斷使用,隊列所能容納的元素總數會不斷減少。如圖所示
基於這種問題,我們采用循環數組的形式來表示一個隊列,循環數組的結構大致如下圖所示:
這種隊列的頭指針不一定小於尾指針,當隊首元素元素位於數組的尾部,這個時候只要數組中仍然有空閑位置來存儲數據的時候,可以將隊首指針再次指向數組的頭部,從而實現空間的重復利用.
在這種情況下隊列的頭部指針的計算公式為: head = (head + 1) % MAXSIZE,尾部的計算公式為 tail = (tail + 1) % MAXSIZE
當隊列為空時應該是 head == tail,註意,由於數組是循環數組,此時並不一定能保證它們二者都為0,只有當隊列中從來沒有過數據,也就是說是剛剛初始化完成的時候它們才都為0
那麽隊列為滿的情況又怎麽確定呢?在使用普通數組的時候,當二者相等都為MAXSIZE的時候隊列為滿,但是在循環隊列中,並不能根據二者都等於MAXSIZE來判斷隊列是否已滿,有可能之前進行過出隊的操作,所以在隊列頭之前的位置仍然能存數據,所以不能根據這個條件判斷。那麽是不是可以根據二者相等來判斷呢?答案是不能,二者相等是用來判斷隊列為空的,那麽怎麽判斷呢?這時候需要空出一塊位置作為隊尾的結束標誌,回想一下給出的循環數組的實例圖,每當head與tail只間隔一個元素的時候作為隊列已滿的標識。這個時候判斷的公式為 (tail + 1) % MAXSIZE == head
下面給出各種操作對應的代碼
隊列的初始化
int g_Queue[MAX_SIZE] = {0};
int g_front = 0;
int g_real = 0;
bool InitQueue()
{
g_front = g_real = 0;
memset(g_Queue, 0x00, sizeof(g_Queue));
return true;
}
入隊
bool EnQueue(int nVal)
{
if (IsQueueFull())
{
return false;
}
g_Queue[g_real] = nVal;
g_real = (g_real + 1) % MAX_SIZE;
return true;
}
出隊
bool DeQueue(int *pVal)
{
if (NULL == pVal)
{
return false;
}
if (IsQueueFull())
{
return false;
}
*pVal = g_Queue[g_front];
g_front = (g_front + 1) % MAX_SIZE;
return true;
}
判斷隊空與隊滿
bool IsQueueEmpty()
{
return g_real == g_front;
}
bool IsQueueFull()
{
return (( g_real + 1 ) % MAX_SIZE) == g_front;
}
最後從實現上來看基於數組的隊列要比基於鏈表的簡單許多,不需要考慮空指針,內存分配的問題,但是基於數組的隊列需要額外考慮是否已滿的情況,當然這個問題可以使用動態數組來避免。
算法與數據結構(二):隊列