1. 程式人生 > >算法與數據結構(二):隊列

算法與數據結構(二):隊列

業務 鏈表 == 頭指針 簡單實現 type 保存 簡單 插入

隊列也是一種線性的數據結構,它與鏈表的區別在於鏈表可以在任意位置進行插入刪除操作,而隊列只能在一端進行插入,另一端進行刪除。它對應於現實世界中的排隊模型。隊列有兩種常見的實現方式:基於列表的實現和基於數組的實現

基於鏈表的實現

基於鏈表的隊列,我們需要保存兩個指針,一個指向頭節點,一個指向尾節點。

這種隊列不存在隊列滿的情況,當然也可以根據業務需求給定一個最大值。當頭結點為空時表示隊列為空。由於隊列只能在隊尾插入數據,隊首刪除數據,因此針對隊列的插入需要采用鏈表的尾插法,隊列元素的出隊需要改變頭指針。

隊列節點的定義與鏈表節點的定義相同,下面給出各種操作的簡單實現

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;
}

最後從實現上來看基於數組的隊列要比基於鏈表的簡單許多,不需要考慮空指針,內存分配的問題,但是基於數組的隊列需要額外考慮是否已滿的情況,當然這個問題可以使用動態數組來避免。


算法與數據結構(二):隊列