數據結構14:隊列(Queue),“先進先出”的數據結構


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

圖1 隊列示意圖

稱進入隊列的一端為“隊尾”;出隊列的一端為“隊頭”。數據元素全部由隊尾陸續進隊列,由隊頭陸續出隊列。

隊列的先進先出原則

隊列從一端存入數據,另一端調取數據的原則稱為“先進先出”原則。(first in first out,簡稱“FIFO”)

圖1中,根據隊列的先進先出原則,(a 1,a 2,a 3,…,a n)中,由於 a 最先從隊尾進入隊列,所以可以最先從隊頭出隊列,對於 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隊列為空

 

使用鏈隊列的心得體會

在使用鏈隊列時,最簡便的方法就是鏈表的表頭一端表示隊列的隊頭,表的另一端表示隊列的隊尾,這樣的設置會使程序更簡單。

反過來的話,隊列在增加元素的時候,要采用頭插法,在刪除數據元素的時候,由於要先進先出,需要刪除鏈表最末端的結點,就需要將倒數第二個結點的next指向NULL,這個過程是需要遍歷鏈表的。


另外需要注意的是,在刪除隊列中數據元素的時候,每次都需要判斷隊列是否為空,這就需要尋找一個判斷隊列為空的條件:如果頭結點的指針域為NULL,說明隊列為空;如果隊頭和隊尾指針都指向頭結點,說明隊列為空。(二選一)

使用鏈隊列解決問題時,要避免“野指針”的出現:

1.當刪除最后一個數據元素時,由於一貫地認為數據元素出隊列只跟隊頭指針有關系,會忽略隊尾指針。
2.當鏈隊列中只剩有一個數據元素時,隊尾指針指向的就是這個數據元素,被刪除后,隊尾指針指向的內存空間被釋放,還有可能給別的程序使用。
  這時候,隊尾指針如果不進行重定義,就會變成“野指針”。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM