隊列是線性表的一種,在操作數據元素時,和棧一樣,有自己的規則:使用隊列存取數據元素時,數據元素只能從表的一端進入隊列,另一端出隊列,如圖1。

稱進入隊列的一端為“隊尾”;出隊列的一端為“隊頭”。數據元素全部由隊尾陸續進隊列,由隊頭陸續出隊列。
隊列的先進先出原則
隊列從一端存入數據,另一端調取數據的原則稱為“先進先出”原則。(first in first out,簡稱“FIFO”)圖1中,根據隊列的先進先出原則,(a 1,a 2,a 3,…,a n)中,由於 a 1 最先從隊尾進入隊列,所以可以最先從隊頭出隊列,對於 a 2 來說,只有 a 1 出隊之后,a 2 才能出隊。
類似於日常生活中排隊買票,先排隊(入隊列),等自己前面的人逐個買完票,逐個出隊列之后,才輪到你買票。買完之后,你也出隊列。先進入隊列的人先買票並先出隊列(不存在插隊)。
隊列的實現方式
隊列的實現同樣有兩種方式:順序存儲和鏈式存儲。隊列的順序表示和實現
使用順序存儲結構表示隊列時,首先申請足夠大的內存空間建立一個數組,除此之外,為了滿足隊列從隊尾存入數據元素,從隊頭刪除數據元素,還需要定義兩個指針分別作為頭指針和尾指針。當有數據元素進入隊列時,將數據元素存放到隊尾指針指向的位置,然后隊尾指針增加 1;當刪除對頭元素(即使想刪除的是隊列中的元素,也必須從隊頭開始一個個的刪除)時,只需要移動頭指針的位置就可以了。
順序表示是在數組中操作數據元素,由於數組本身有下標,所以隊列的頭指針和尾指針可以用數組下標來代替,既實現了目的,又簡化了程序。
例如,將隊列(1,2,3,4)依次入隊,然后依次出隊並輸出。
代碼實現:
#include <stdio.h>
int enQueue(int *a, int rear, int data)
{ a[rear] = data; rear++; return rear; }
void deQueue(int *a, int front, int rear)
{ //如果 front==rear,表示隊列為空 while (front != rear)
{ printf("%d", a[front]); front++; } }
int main()
{ int a[100]; int front, rear; //設置隊頭指針和隊尾指針,當隊列中沒有元素時,隊頭和隊尾指向同一塊地址 front = rear = 0; //入隊 rear = enQueue(a, rear, 1); rear = enQueue(a, rear, 2); rear = enQueue(a, rear, 3); rear = enQueue(a, rear, 4); //出隊 deQueue(a, front, rear); return 0; }
順序存儲存在的問題
當使用線性表的順序表示實現隊列時,由於按照先進先出的原則,隊列的隊尾一直不斷的添加數據元素,隊頭不斷的刪除數據元素。由於數組申請的空間有限,到某一時間點,就會出現 rear 隊列尾指針到了數組的最后一個存儲位置,如果繼續存儲,由於 rear 指針無法后移,就會出錯。
數組真的滿了嗎?隊頭由於刪除元素,front 后移, front 前邊還會有可以使用的空間。所以為了充分利用這部分空間,可以考慮使用下面這種方式。
順序存儲的升級版
使用數組存取數據元素時,可以將數組申請的空間想象成首尾連接的環狀空間使用。例如,在申請的內存空間大小為 5 的情況下,將數字 1-6 進隊后再出隊(普通方式中 6 是無法進隊的):
代碼實現:
#include <stdio.h> #define max 5
int enQueue(int *a, int front, int rear, int data)
{ //循環隊列中,如果尾指針和頭指針重合,證明數組存放的數據已滿 if ((rear+1)%max == front)
{ printf("空間已滿"); return rear; } a[rear%max] = data; rear++; return rear; }
int deQueue(int *a, int front, int rear)
{ //如果front==rear,表示隊列為空 if(front == rear)
{ printf("隊列為空"); return front; } printf("%d", a[front]); front = (front+1)%max; return front; }
int main()
{ int a[max]; int front, rear; //設置隊頭指針和隊尾指針,當隊列中沒有元素時,隊頭和隊尾指向同一塊地址 front = rear = 0; //入隊 rear = enQueue(a, front, rear, 1); rear = enQueue(a, front, rear, 2); rear = enQueue(a, front, rear, 3); rear = enQueue(a, front, rear, 4); //出隊 front = deQueue(a, front, rear); rear = enQueue(a, front, rear, 5); front = deQueue(a, front, rear); rear = enQueue(a, front, rear, 6); front = deQueue(a, front, rear); front=deQueue(a, front, rear); front=deQueue(a, front, rear); front=deQueue(a, front, rear); return 0; }
運行結果:
123456
在使用循環隊列判斷數組是否已滿時,出現下面情況:
- 當隊列為空時,隊列的頭指針等於隊列的尾指針
- 當數組滿員時,隊列的頭指針等於隊列的尾指針
要將空隊列和隊列滿的情況區分開,辦法是:犧牲掉數組中的一個存儲空間,判斷數組滿員的條件是:尾指針的下一個位置和頭指針相遇,就說明數組滿了。
隊列的鏈式表示和實現(簡稱為“鏈隊列”)
隊列的鏈式存儲是在鏈表的基礎上,按照“先進先出”的原則操作數據元素。
例如,將隊列(1,2,3,4)依次入隊,然后再依次出隊。
代碼實現:
#include <stdio.h> #include <stdlib.h>
typedef struct QNode
{ int data; struct QNode *next; }QNode;
QNode *initQueue()
{ QNode *queue = (QNode*)malloc(sizeof(QNode)); queue->next = NULL; return queue; }
QNode *enQueue(QNode *rear, int data)
{ QNode *enElem = (QNode*)malloc(sizeof(QNode)); enElem->data = data; enElem->next = NULL; //使用尾插法向鏈隊列中添加數據元素 rear->next = enElem; rear = enElem; return rear; }
void DeQueue(QNode *front, QNode *rear)
{ if (front->next == NULL)
{ printf("隊列為空"); return ; } QNode *p = front->next; printf("%d", p->data); front->next = p->next; if (rear == p)
{ rear = front; } free(p); }
int main()
{ QNode *queue, *front, *rear; queue = front = rear = initQueue(); //創建頭結點 //向鏈隊列中添加結點,使用尾插法添加的同時,隊尾指針需要指向鏈表的最后一個元素 rear = enQueue(rear, 1); rear = enQueue(rear, 2); rear = enQueue(rear, 3); rear = enQueue(rear, 4); //入隊完成,所有數據元素開始出隊列 DeQueue(front, rear); DeQueue(front, rear); DeQueue(front, rear); DeQueue(front, rear); DeQueue(front, rear);
return 0; }
運行結果:
1234隊列為空
使用鏈隊列的心得體會
在使用鏈隊列時,最簡便的方法就是鏈表的表頭一端表示隊列的隊頭,表的另一端表示隊列的隊尾,這樣的設置會使程序更簡單。
另外需要注意的是,在刪除隊列中數據元素的時候,每次都需要判斷隊列是否為空,這就需要尋找一個判斷隊列為空的條件:如果頭結點的指針域為NULL,說明隊列為空;如果隊頭和隊尾指針都指向頭結點,說明隊列為空。(二選一)
使用鏈隊列解決問題時,要避免“野指針”的出現:
1.當刪除最后一個數據元素時,由於一貫地認為數據元素出隊列只跟隊頭指針有關系,會忽略隊尾指針。 2.當鏈隊列中只剩有一個數據元素時,隊尾指針指向的就是這個數據元素,被刪除后,隊尾指針指向的內存空間被釋放,還有可能給別的程序使用。
這時候,隊尾指針如果不進行重定義,就會變成“野指針”。