数据结构与算法 -- 队列 ADT
像棧一樣,隊列(queue)也是表。然而,使用隊列時插入在一端進行而刪除則在另一端進行,也就是先進先出(FIFO)。隊列的基本操作是EnQueue(入隊),它是在表的末端(叫做隊尾(rear))插入一個元素;還有DeQueue(出隊),它是刪除(或返回)在表的開頭(叫做隊頭(front))的元素。隊列中沒有元素時,稱為空隊列。
隊列需要兩個指針,一個指向隊頭,一個指向隊尾。對棧的操作永遠都是針對棧頂(top)進行的。但是,當隊列的元素插入和刪除時,它所使用的是數組中的不同的元素。
隊列一般有鏈式隊列和順序隊列兩種。鏈式隊列相當于我們在銀行中排隊,后來的人排到隊伍的最后,前面的人辦理完業務后就會離開,讓下一個人進去;順序隊列則是采用循環數組實現,讓隊列的尾部“環繞”到數組的頭部,這樣新元素就可以存儲到以前刪除元素所留出來的空間中。
一、順序隊列
和棧類似,其中又分為靜態數組和動態數組兩類。
靜態數組:特點是要求結構的長度固定,而且長度在編譯時候就得確定。其優點是結構簡單,實現起來方便而不容易出錯。而缺點就是不夠靈活以及固定長度不容易控制,適用于知道明確長度的場合。
動態數組:特點是長度可以在運行時候才確定以及可以更改原來數組的長度。優點是靈活,缺點是由此會增加程序的復雜性。
前面提到順序隊列采用循環數組實現,但是使用循環數組自身也會引入一個問題。當front 和 rear的值相同時,這和隊列已滿時的情況是一樣的。當隊列空或者滿時對 front 和 rear 進行比較,其結果都是真。所以,我們無法通過比較 front 和 rear 來測試隊列是否為空。
有兩種方法可以解決這個問題:
第一種方法:對這個變量的值進行測試就可以很容易分清隊列空間為空還是為滿。
第二種方法:重定義“滿”的含義。如果使數組中的一個元素始終保留不用,這樣當隊列“滿”時 front 和 rear 的值便不相同,可以和隊列為空時的情況區分開來。
代碼實現說明:
首先我們來說下取余,之前有講過的,參看:C語言再學習 -- 運算符與表達式
取余運算符用于整數運算,該運算符計算出用它右邊的整數去除它左邊的整數得到的余數。
注意:不要對浮點數使用該運算符,那將是無效的。
負數取余規則:
如果第一個操作數為負數,那么得到的余數也為負數;如果第一個操作數為正數,那么得到的余數也為正數。
11 % 5 = 1;11 % -5 = 1;-11 % 5 = -1;-11 % -5 = -1;
4、自增和自減:++和--
后綴: a++; ?a--; ?使用a的值之后改變a;
前綴: ++a; ?--a; ?使用a的值之前改變a;
在隊列中我們用不到負數的,不過還是讓你看一下取余的結果:
#include <stdio.h>int main (void) {printf ("0%%5 = %d\n", 0%5);printf ("1%%5 = %d\n", 1%5);printf ("2%%5 = %d\n", 2%5);printf ("3%%5 = %d\n", 3%5);printf ("4%%5 = %d\n", 4%5);printf ("5%%5 = %d\n", 5%5);printf ("6%%5 = %d\n", 6%5);printf ("7%%5 = %d\n", 7%5);return 0; } 輸出結果: 0%5 = 0 1%5 = 1 2%5 = 2 3%5 = 3 4%5 = 4 5%5 = 0 6%5 = 1 7%5 = 2了解了取余后,就不難理解順序隊列了,其實也是操作的數組下標,只不過使用了前/后兩個下標表示。
下面靜態數組和動態數組參看于 C 和指針,你有沒有看出來有什么問題?
首先,數組第一元素 queue[0],未賦值!! 不過宏定義的時候數組長度加 1 了。不過你最好是知道數組第一個元素是queue[0] 而不是 queue[1]。
最后它的判空判滿,不夠魯棒。
1、靜態數組
#include <stdio.h> #include <stdlib.h> #include <assert.h>#define QUEUE_TYPE int /* 隊列元素的類型 */#define QUEUE_SIZE 100 /* 隊列中元素的最大數量 */ #define ARRAY_SIZE (QUEUE_SIZE + 1) /* 數組的長度 *//* 用于存儲隊列元素的數組和指向隊列頭和尾的指針 */ static QUEUE_TYPE queue[ARRAY_SIZE]; static size_t front = 1; static size_t rear = 0;/* 向隊列添加一個新元素,參數就是需要添加的元素 */ void insert (QUEUE_TYPE value);/* 從隊列中移除一個元素并將其丟棄 */ void delete (void);/* 返回隊列中第一個元素的值,但不修改隊列本身 */ QUEUE_TYPE first (void);/* 如果隊列為空,返回TRUE,否則返回FALSE */ int is_empty (void);/* 如果隊列已滿,返回TRUE,否則返回FALSE */ int is_full (void);/* 自定義函數實現遍歷操作 */ void travel (void);/* 計算隊列中元素的個數 */ int size (void);int main (void) {travel ();delete ();printf("%s\n", is_empty() ? "隊列已空" : "隊列未空"); int i = 0; for (i = 0; i <= 9; i++) { insert (i); } puts ("經過 insert 入隊后的元素值為: "); travel (); printf ("此時隊列首元素為:%d\n", first ()); printf ("此時隊列元素個數為:%d\n", size ()); delete ();delete ();puts ("經過 delete 出隊幾個元素后的隊列元素: "); travel (); printf("%s\n", is_full() ? "隊列已滿" : "隊列未滿"); printf("%s\n", is_empty() ? "隊列已空" : "隊列未空"); printf ("此時隊列首元素為:%d\n", first ()); printf ("此時隊列元素個數為:%d\n", size ()); return 0; }void insert (QUEUE_TYPE value) {//assert (!is_full ());if (is_full ()){printf ("隊列已滿,入隊列失敗\n");return ;}rear = (rear + 1) % ARRAY_SIZE;queue[rear] = value; }void delete (void) {//assert (!is_empty ());if (is_empty ()){printf ("隊列已空,出隊列失敗\n"); return ;}front = (front + 1) % ARRAY_SIZE; }QUEUE_TYPE first (void) {assert (!is_empty ());if (is_empty ())return ;return queue[front]; }int is_empty (void) {return (rear + 1) % ARRAY_SIZE == front; }int is_full (void) {return (rear + 2) % ARRAY_SIZE == front; }void travel (void) {printf ("隊列中的元素有:");int i = 0;for (i = front; i < rear; i++)printf ("%d ", queue[i % ARRAY_SIZE]);printf ("\n"); }int size (void) {return rear - front; } 隊列中的元素有: 隊列已空,出隊列失敗 隊列已空 經過 insert 入隊后的元素值為: 隊列中的元素有:0 1 2 3 4 5 6 7 8 此時隊列首元素為:0 此時隊列元素個數為:9 經過 delete 出隊幾個元素后的隊列元素: 隊列中的元素有:2 3 4 5 6 7 8 隊列未滿 隊列未空 此時隊列首元素為:2 此時隊列元素個數為:72、動態數組
#include <stdio.h> #include <stdlib.h> #include <assert.h>#define QUEUE_TYPE int /* 隊列元素的類型 *//* 用于存儲隊列元素的數組和指向隊列頭和尾的指針 */static QUEUE_TYPE *queue; static int queue_size; static int array_size;static size_t front = 1; static size_t rear = 0;/* 創建一個隊列,參數指定隊列可以存儲的元素的最大數量 注意:這個函數只適用于使用動態分配數組的隊列*/ void create_queue (size_t size);/* 銷毀一個隊列 注意:這個函數只適用于鏈式和動態分配數組的隊列*/ void destroy_queue (void);/* 向隊列添加一個新元素,參數就是需要添加的元素 */ void insert (QUEUE_TYPE value);/* 從隊列中移除一個元素并將其丟棄 */ void delete (void);/* 返回隊列中第一個元素的值,但不修改隊列本身 */ QUEUE_TYPE first (void);/* 如果隊列為空,返回TRUE,否則返回FALSE */ int is_empty (void);/* 如果隊列已滿,返回TRUE,否則返回FALSE */ int is_full (void);/* 自定義函數實現遍歷操作 */ void travel (void);/* 計算隊列中元素的個數 */ int size (void);int main (void) {create_queue (50);travel ();delete ();printf("%s\n", is_empty() ? "隊列已空" : "隊列未空"); int i = 0; for (i = 0; i <= 9; i++) { insert (i); } puts ("經過 insert 入隊后的元素值為: "); travel (); printf ("此時隊列首元素為:%d\n", first ()); printf ("此時隊列元素個數為:%d\n", size ()); delete ();delete ();puts ("經過 delete 出隊幾個元素后的隊列元素: "); travel (); printf("%s\n", is_full() ? "隊列已滿" : "隊列未滿"); printf("%s\n", is_empty() ? "隊列已空" : "隊列未空"); printf ("此時隊列首元素為:%d\n", first ()); printf ("此時隊列元素個數為:%d\n", size ()); destroy_queue ();printf ("此時隊列元素個數為:%d\n", size ()); return 0; }void create_queue (size_t size) {//assert (array_size == 0);if (size < array_size){printf ("隊列元素太少\n");}array_size = size;queue = malloc (sizeof (QUEUE_TYPE) * array_size);if (NULL == queue)perror ("malloc分配失敗"), exit (1); }void destroy_queue (void) {//assert (queue_size > 0);if (queue != NULL){printf ("銷毀隊列\n");array_size = 0;free (queue);queue = NULL;front = 0;rear = 0;} }void insert (QUEUE_TYPE value) {//assert (!is_full ());if (is_full ()){printf ("隊列已滿,入隊列失敗\n");return ;}rear = (rear + 1) % array_size;queue[rear] = value; }void delete (void) {//assert (!is_empty ());if (is_empty ()){printf ("隊列已空,出隊列失敗\n"); return ;}front = (front + 1) % array_size; }QUEUE_TYPE first (void) {assert (!is_empty ());if (is_empty ())return ;return queue[front]; }int is_empty (void) {//assert (queue_size > 0);if (queue != NULL){return (rear + 1) % array_size == front;} }int is_full (void) {//assert (queue_size > 0);if (queue != NULL){return (rear + 2) % array_size == front;} }void travel (void) {printf ("隊列中的元素有:");int i = 0;for (i = front; i < rear; i++)printf ("%d ", queue[i % array_size]);printf ("\n"); }int size (void) {return rear - front; } 輸出結果: 隊列中的元素有: 隊列已空,出隊列失敗 隊列已空 經過 insert 入隊后的元素值為: 隊列中的元素有:0 1 2 3 4 5 6 7 8 此時隊列首元素為:0 此時隊列元素個數為:9 經過 delete 出隊幾個元素后的隊列元素: 隊列中的元素有:2 3 4 5 6 7 8 隊列未滿 隊列未空 此時隊列首元素為:2 此時隊列元素個數為:7 銷毀隊列 此時隊列元素個數為:03、自寫順序隊列
//采用順序存儲結構實現隊列的操作 #include <stdio.h> #define SIZE 5//定義隊列的數據類型 int arr[SIZE]; //存儲元素的位置 int front; //記錄隊首元素的下標 int rear; //下一個可以存放元素下標//入隊操作 值傳遞 址傳遞 void push (int data);//遍歷隊列中的所有元素 void travel (void);//出隊操作 int pop (void);//判斷隊列是否為空 int empty (void);//判斷隊列是否為滿 int full (void);//查看隊首元素 int get_head (void);//查看隊尾元素 int get_tail (void);//計算隊列中元素個數 int size (void);int main (void) {//創建隊列,并且進行初始化push(11);travel(); //11push(22);travel(); //11 22push(33);travel(); //11 22 33push(44);travel(); //11 22 33 44 push(55);travel(); //11 22 33 44 55printf("出隊的元素是:%d\n",pop());travel(); //22 33 44 55printf("出隊的元素是:%d\n",pop());travel(); //33 44 55push(66);travel(); //33 44 55 66push(77);travel(); //33 44 55 66 77printf("------------------\n");printf("隊首元素是:%d\n",get_head());printf("隊尾元素是:%d\n",get_tail());printf("隊列中元素個數是:%d\n",size());printf("%s\n",empty()?"隊列已經空了":"隊列沒有空");printf("%s\n",full()?"隊列已經滿了":"隊列沒有滿");return 0; }void push (int data) {if (full ())return ;arr[rear % 5] = data;rear++; }void travel (void) {printf ("隊列中的元素有:");int i = 0;for (i = front; i < rear; i++){printf ("%d ", arr[i % 5]);}printf ("\n"); }int pop (void) {if (empty ())return ;int temp = arr[front % 5];front++;return temp; }int size (void) {return rear - front; }int get_tail (void) {if (empty ())return ;return arr[(rear - 1) % 5]; }int get_head (void) {if (empty ())return ;return arr[front %5]; }int full (void) {return SIZE == rear - front; }int empty (void) {return 0 == rear - front; } 輸出結果: 隊列中的元素有:11 隊列中的元素有:11 22 隊列中的元素有:11 22 33 隊列中的元素有:11 22 33 44 隊列中的元素有:11 22 33 44 55 出隊的元素是:11 隊列中的元素有:22 33 44 55 出隊的元素是:22 隊列中的元素有:33 44 55 隊列中的元素有:33 44 55 66 隊列中的元素有:33 44 55 66 77 ------------------ 隊首元素是:33 隊尾元素是:77 隊列中元素個數是:5 隊列沒有空 隊列已經滿了4、順序隊列總結
(1)預分配空間 由用戶指定隊列最多能容納得元素個數,即隊列容量,分配足量的內存,記下隊列容量,并將前/后端指針和元素個數初始化為 0 (2)壓入彈出 被壓入的元素放在后端指針處,同時后端指針上移,被彈出的元素從前端指針處獲得,同時前端指針上移。 (3)循環使用 如果前端或后端指針大于等于隊列容量,且隊列不空或不滿,則將該指針循環復位至 0 (4)判空判滿 元素個數為 0 則空,禁止彈出,元素個數大于等于隊列容量則滿,禁止壓入。二、鏈式隊列
鏈式隊列(FIFO)和鏈式棧(LIFO)作比較很明顯可以看出來區別:壓入時,一個是向后排隊,一個是向上堆;彈出時,一個是從前端彈出,一個是從棧頂彈出。理解了這些,再看代碼就簡單了。 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??1、鏈式隊列
#include <stdio.h> #include <stdlib.h> #define QUEUE_TYPE int /* 隊列所存儲的值的數據類型 */ /* 定義一個結構以存儲隊列元素。 */ typedef struct node { QUEUE_TYPE value; struct node *next; }QueNode, *QuePtr; typedef struct { QuePtr front, rear; }Queue; /*初始化隊列*/ void init_queue (Queue *q); /*創建隊列,由于沒有長度限制,故不需要create_stack函數*/ void create_queue (Queue *q); //入隊操作 void push (Queue *q, QUEUE_TYPE value); //出隊操作 int pop (Queue *q); //判斷隊列是否為滿 int is_full (Queue *q); //判斷隊列是否為空 int is_empty (Queue *q); //計算隊列中節點的個數 int size (Queue *q); //遍歷操作 void travel (Queue *q); //清空隊列中所有的元素 void destroy_queue (Queue *q); //獲取隊首元素的值 int top (Queue *q); //獲取隊尾元素的值 int tail (Queue *q); int main (void) { Queue queue; init_queue (&queue); push (&queue, 11); travel (&queue); push (&queue, 22); travel (&queue); push (&queue, 33); travel (&queue); push (&queue, 44); travel (&queue); push (&queue, 55); travel (&queue); printf("出隊的元素是:%d\n",pop(&queue)); travel (&queue); printf("出隊的元素是:%d\n",pop(&queue)); travel (&queue); push(&queue,66); travel(&queue); //33 44 55 66 push(&queue,77); travel(&queue); //33 44 55 66 77 printf("------------------\n"); printf("%s\n",is_empty(&queue)?"隊列已經空了":"隊列沒有空"); printf("%s\n",is_full(&queue)?"隊列已經滿了":"隊列沒有滿"); printf("隊首元素是:%d\n",top (&queue)); printf("隊尾元素是:%d\n",tail(&queue)); printf("隊列中的元素個數是:%d\n",size(&queue)); printf("---------------------\n"); travel(&queue); printf("出隊的元素是:%d\n",pop(&queue)); destroy_queue (&queue); travel(&queue); return 0; } void init_queue (Queue *q) { q->front = (QuePtr)malloc (sizeof (QueNode)); if (NULL == q->front) perror ("malloc分配失敗"), exit (1); q->front->next = NULL; q->rear = q->front; } void create_queue (Queue *q) { } void destroy_queue (Queue *q) { QuePtr temp, p; if (q->front != NULL) { p = q->front->next; while (p != NULL) { temp = p->next; free (p); p = temp; } q->front->next = NULL; q->rear = q->front; } } void push (Queue *q, QUEUE_TYPE value) { QuePtr p = (QuePtr)malloc (sizeof (QueNode)); if (NULL == p) perror ("malloc分配失敗"); p->value = value; p->next = NULL; if (q->rear != NULL)q->rear->next = p; q->rear = p; if (q->front == NULL)q->front = q->rear; } int pop (Queue *q) { if (is_empty (q)) return ; QuePtr p = q->front->next; QUEUE_TYPE i = p->value; q->front->next = p->next; if (q->rear == p) q->rear = q->front; free (p); p = NULL; return i; } int is_full (Queue *q) { return 0; } int is_empty (Queue *q) { return q->front == q->rear; } int size (Queue *q) { QuePtr p = q->front; int i = 0; while (p != q->rear) { ++i; p = p->next; } return i; } void travel (Queue *q) { printf ("隊列中的元素有:"); if (NULL == q->front) { printf ("隊列不存在\n"); return ; } else if (q->front == q->rear) { printf ("隊列已空\n"); return ; } else { QuePtr p = NULL; p = q->front->next; while (p != q->rear) { printf ("%d ", p->value); p = p->next; } printf ("%d ", p->value); printf ("\n"); } } int top (Queue *q) { if (is_empty (q)) return ; return q->front->next->value; } int tail (Queue *q) { if (is_empty (q)) return ; return q->rear->value; } 輸出結果: 隊列中的元素有:11 隊列中的元素有:11 22 隊列中的元素有:11 22 33 隊列中的元素有:11 22 33 44 隊列中的元素有:11 22 33 44 55 出隊的元素是:11 隊列中的元素有:22 33 44 55 出隊的元素是:22 隊列中的元素有:33 44 55 隊列中的元素有:33 44 55 66 隊列中的元素有:33 44 55 66 77 ------------------ 隊列沒有空 隊列沒有滿 隊首元素是:33 隊尾元素是:77 隊列中的元素個數是:5 --------------------- 隊列中的元素有:33 44 55 66 77 出隊的元素是:33 隊列中的元素有:隊列已空2、自寫鏈式隊列
//基于鏈式結構的隊列操作 #include <stdio.h> #include <stdlib.h>//定義節點的數據類型 typedef struct Node {int data; //存放具體的數據內容struct Node *next; //下一個節點地址 }Node;//定義隊列的數據類型 typedef struct {Node *head; //保存第一個節點的地址 }Queue;//入隊操作 void push (Queue *pq, int data);//遍歷操作 void travel (Queue *pq);//判斷隊列是否為空 int empty (Queue *pq);//判斷隊列是否為滿 int full (Queue *pq);//獲取隊首元素的值 int get_head (Queue *pq);//獲取隊尾元素的值 int get_tail (Queue *pq);//計算隊列中節點的個數 int size (Queue *pq);//出隊操作 int pop (Queue *pq);//清空隊列中所有的元素 void clear (Queue *pq);int main (void) {//1.創建隊列并且進行初始化Queue queue;queue.head = NULL;push(&queue,11);travel(&queue); //11push(&queue,22);travel(&queue); //11 22push(&queue,33);travel(&queue); //11 22 33push(&queue,44);travel(&queue); //11 22 33 44push(&queue,55);travel(&queue); //11 22 33 44 55printf("出隊的元素是:%d\n",pop(&queue));travel(&queue); //22 33 44 55printf("出隊的元素是:%d\n",pop(&queue));travel(&queue); //33 44 55push(&queue,66);travel(&queue); //33 44 55 66push(&queue,77);travel(&queue); //33 44 55 66 77printf("------------------\n");printf("%s\n",empty(&queue)?"隊列已經空了":"隊列沒有空");printf("%s\n",full(&queue)?"隊列已經滿了":"隊列沒有滿");printf("隊首元素是:%d\n",get_head(&queue));printf("隊尾元素是:%d\n",get_tail(&queue));printf("隊列中的元素個數是:%d\n",size(&queue));printf("---------------------\n");travel(&queue);printf("出隊的元素是:%d\n",pop(&queue));clear(&queue);travel(&queue);return 0; }void push (Queue *pq, int data) {//1.創建新節點Node *pn = (Node *)malloc (sizeof (Node));pn->data = data;pn->next = NULL;//2.掛接新節點到隊列的尾部//2.1 如果隊列為空,則直接連接if (NULL == pq->head)pq->head = pn;//2.2 如果隊列不為空,使用尾節點連接新節點else{Node *p = pq->head;// p指向的節點不是尾節點,則尋找下一個節點 再比較while (p->next != NULL)//指向下一個節點p = p->next;//使用尾節點連接新節點p->next = pn;} }void travel (Queue *pq) {printf ("隊列中的元素有:");Node *p = pq->head;while (p != NULL){printf ("%d ", p->data);p = p->next;}printf ("\n"); }int size (Queue *pq) {int count = 0;Node *p = pq->head;while (p != NULL){count++;p = p->next;}return count; }int get_tail (Queue *pq) {if (empty (pq))return ;Node *p = pq->head;while (p->next != NULL)p = p->next;return p->data; }int get_head (Queue *pq) {if (empty (pq))return ;return pq->head->data; }int full (Queue *pq) {return 0; }int empty (Queue *pq) {return NULL == pq->head; }int pop (Queue *pq) {if (empty (pq))return ;//保存第一個節點的地址Node *p = pq->head;//頭指針指向第二個節點pq->head = pq->head->next;//保存要刪除的節點數據int temp = p->data;//刪除第一個節點free (p);p = NULL;return temp; }void clear (Queue *pq) {while (pq->head != NULL){//保存第一個節點地址Node *p = pq->head;//頭指針指向下一個節點pq->head = pq->head->next;//釋放第一個節點free (p);p = NULL;} } 輸出結果: 隊列中的元素有:11 隊列中的元素有:11 22 隊列中的元素有:11 22 33 隊列中的元素有:11 22 33 44 隊列中的元素有:11 22 33 44 55 出隊的元素是:11 隊列中的元素有:22 33 44 55 出隊的元素是:22 隊列中的元素有:33 44 55 隊列中的元素有:33 44 55 66 隊列中的元素有:33 44 55 66 77 ------------------ 隊列沒有空 隊列沒有滿 隊首元素是:33 隊尾元素是:77 隊列中的元素個數是:5 --------------------- 隊列中的元素有:33 44 55 66 77 出隊的元素是:33 隊列中的元素有:3、鏈式隊列總結
(1)無需預分配 不需要預分配存儲空間,不需要記住隊列容量,也不需要判斷隊列是否滿,隨壓入隨分配,隨彈出隨釋放,提升內存空間利用率。 (2)壓入彈出 被壓入的元素放在新建節點中,令后端節點的后指針指向該節點,并令其成為新的后端節點,被彈出的元素由前端節點獲得,釋放前端節點,并令其后節點成為新的前端節點。 (3)注意判空 前/后端指針為空表示隊列已空,此時不可再彈出。總結
以上是生活随笔為你收集整理的数据结构与算法 -- 队列 ADT的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构与算法 -- 栈 ADT
- 下一篇: 数据结构与算法 -- 链表