程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 隊列的存儲結構和常見操作(c 語言實現)

隊列的存儲結構和常見操作(c 語言實現)

編輯:關於C語言

隊列的存儲結構和常見操作(c 語言實現)


一、隊列(queue)   隊列和棧一樣,在實際程序的算法設計和計算機一些其他分支裡,都有很多重要的應用,比如計算機操作系統對進程 or 作業的優先級調度算法,對離散事件的模擬算法,還有計算機主機和外部設備運行速度不匹配的問題解決等,很多很多。其實隊列的本質還是線性表!只不過是一種特殊的或者說是受限的線性表,是這樣的:   1)、限定在表的一端插入、另一端刪除。 插入的那頭就是隊尾,刪除的那頭就是隊頭。也就是說只能在線性表的表頭刪除元素,在表尾插入元素。形象的說就是水龍頭和水管,流水的水嘴是隊頭,進水的泵是隊尾,管子中間不漏水不進水。這樣呲呲的流動起來,想想就是這麼個過程。   2)、先進先出 (FIFO結構)。顯然我們不能在表(隊列)的中間操作元素,只能是在尾部進,在頭部出去,還可以類似火車進隧道的過程。(first in first out = FIFO 結構)       注意,當隊列沒有元素的時候,我們就說隊列是空隊列。       1、雙端隊列   double-ended queue:限定插入和刪除在表的兩端進行,也是先進先出 (FIFO)結構,類似鐵路的轉軌網絡。實際程序中應用不多。       這種結構又細分為三類:   1)、輸入受限的雙端隊列:一個端點可插入和刪除,另一個端點僅可刪除。   2)、輸出受限的雙端隊列:一個端點可插入和刪除,另一個端點僅可插入。      3)、等價於兩個棧底相連接的棧:限定雙端隊列從某個端點插入的元素,只能在此端點刪除。       2、鏈隊(有鏈的地方,就有指針)   用鏈表表示的隊列,限制僅在表頭刪除和表尾插入的單鏈表。一個鏈隊列由一個頭指針和一個尾指針唯一確定。(因為僅有頭指針不便於在表尾做插入操作)。為了操作的方便,也給鏈隊列添加一個頭結點,因此,空隊列的判定條件是:頭指針和尾指針都指向頭結點。       之前的鏈式結構,總是使用一個結點的結構來表示鏈表,其實不太方便,這裡使用新的存儲結構。定義一個結點結構,和一個隊列結構。兩個結構嵌套。   復制代碼  1 #ifndef queue_Header_h  2 #define queue_Header_h  3 #include <stdio.h>  4 #include <stdlib.h>  5 #include <stdbool.h>  6   7 //隊列的結點結構  8 typedef struct Node{  9     int data; 10     struct Node *next; 11 } Node, *Queue; 12  13 //隊列的結構,嵌套 14 typedef struct{ 15     Queue front; 16     Queue rear; 17 } LinkQueue; 18  19 //初始化 20 //開始必然是空隊列,隊尾指針和隊頭指針都指向頭結點 21 void initQueue(LinkQueue *queue) 22 { 23     //初始化頭結點 24     queue->front = queue->rear = (Queue)malloc(sizeof(Node)); 25      26     if (NULL == queue->front) { 27         exit(0); 28     } 29      30     queue->front->next = NULL; 31 } 32  33 //判空 34 bool isEmpty(LinkQueue queue) 35 { 36     return queue.rear == queue.front ? true : false; 37 } 38  39 //入隊,只在一端入隊,另一端出隊,同樣入隊不需要判滿 40 void insertQueue(LinkQueue *queue, int temp) 41 { 42     Queue q = (Queue)malloc(sizeof(Node)); 43      44     if (NULL == q) { 45         exit(0); 46     } 47     //插入數據 48     q->data = temp; 49     q->next = NULL; 50     //rear 總是指向隊尾元素 51     queue->rear->next = q; 52     queue->rear = q; 53 } 54  55 //出隊,需要判空 56 void deleteQueue(LinkQueue *queue) 57 { 58     Queue q = NULL; 59      60     if (!isEmpty(*queue)) { 61         q = queue->front->next; 62         queue->front->next = q->next; 63         //這句很關鍵,不能丟 64         if (queue->rear == q) { 65             queue->rear = queue->front; 66         } 67          68         free(q); 69     } 70 } 71  72 //遍歷 73 void traversal(LinkQueue queue) 74 { 75     int i = 1; 76     Queue q = queue.front->next; 77      78     while (q != NULL) { 79         printf("隊列第%d個元素是:%d\n", i, q->data); 80         q = q->next; 81         i++; 82     } 83 } 84  85 //銷毀 86 void destoryQueue(LinkQueue *queue) 87 { 88     while (queue->front != NULL) { 89         queue->rear = queue->front->next; 90         free(queue->front); 91         queue->front = queue->rear; 92     } 93      94     puts("銷毀成功!"); 95 } 96  97 #endif 復制代碼  測試   復制代碼  1 #include "queue.h"  2   3 int main(int argc, const char * argv[])  4 {  5     LinkQueue queue;  6     puts("初始化隊列 queue");  7     initQueue(&queue);  8     traversal(queue);  9      10     puts("隊尾依次插入0 1 2 3"); 11     insertQueue(&queue, 0); 12     insertQueue(&queue, 1); 13     insertQueue(&queue, 2); 14     insertQueue(&queue, 3); 15     traversal(queue); 16      17     puts("先進先出,刪除隊列從頭開始, 0 "); 18     deleteQueue(&queue); 19     traversal(queue); 20      21     puts("先進先出,刪除隊列從頭開始, 1 "); 22     deleteQueue(&queue); 23     traversal(queue); 24      25     puts("先進先出,刪除隊列從頭開始, 2 "); 26     deleteQueue(&queue); 27     traversal(queue); 28      29     puts("先進先出,刪除隊列從頭開始, 3"); 30     deleteQueue(&queue); 31     traversal(queue); 32      33     destoryQueue(&queue); 34     return 0; 35 } 復制代碼 結果:   初始化隊列 queue   隊尾依次插入0 1 2 3   隊列第1個元素是:0   隊列第2個元素是:1   隊列第3個元素是:2   隊列第4個元素是:3   先進先出,刪除隊列從頭開始, 0    隊列第1個元素是:1   隊列第2個元素是:2   隊列第3個元素是:3   先進先出,刪除隊列從頭開始, 1    隊列第1個元素是:2   隊列第2個元素是:3   先進先出,刪除隊列從頭開始, 2    隊列第1個元素是:3   先進先出,刪除隊列從頭開始, 3   銷毀成功!   Program ended with exit code: 0       3、順序隊列   限制僅在表頭刪除和表尾插入的順序表,利用一組地址連續的存儲單元依次存放隊列中的數據元素。因為隊頭和隊尾的位置是變化的,所以也要設頭、尾指針。     初始化時的頭尾指針,初始值均應置為 0。 入隊尾指針增 1 ,出隊頭指針增 1 。頭尾指針相等時隊列為空,在非空隊列裡,頭指針始終指向隊頭元素,尾指針始終指向隊尾元素的下一位置。   初始為空隊列,那麼頭尾指針相等。       入隊,那麼尾指針加1,頭指針不變。先進先出,J1先進隊,則 rear+1,尾指針始終指向隊尾元素的下一位!如,J2進隊,rear 繼續+1,J3進隊,尾指針繼續加1,如圖           出隊,則尾指針不變,頭指針加1,注意這裡都是加1,先進先出原則,J1先刪除,front+1,指向了 J2,J2刪除,front+1指向了 J3,如圖           最後,J3刪除,則頭指針再次和尾指針相等,說明隊列空了。如圖       在順序隊列中,當尾指針已經指向了隊列的最後一個位置的下一位置時,若再有元素入隊,就會發生“溢出”。如圖位置,再次入隊,就會溢出。           4、循環隊列的誕生   順序隊列的 “假溢出” 問題:隊列的存儲空間未滿,卻發生了溢出。很好理解,比如 rear 現在雖然指向了最後一個位置的下一位置,但是之前隊頭也刪除了一些元素,那麼隊頭指針經歷若干次的 +1 之後,遺留下了很多空位置,但是順序隊列還在傻乎乎的以為再有元素入隊,就溢出呢!肯定不合理。故循環隊列誕生!   解決“假溢出”的問題有兩種可行的方法:   (1)、平移元素:把元素平移到隊列的首部。效率低。否決了。   (2)、將新元素插入到第一個位置上,構成循環隊列,入隊和出隊仍按“先進先出”的原則進行。操作效率高、空間利用率高。            雖然使用循環隊列,解決了假溢出問題,但是又有新問題發生——判空的問題,因為僅憑 front = rear 不能判定循環隊列是空還是滿。比如如圖:   這是空循環隊列的樣子   這是滿循環隊列的樣子   解決辦法:   (1)、另設一個布爾變量以區別隊列的空和滿;   (2)、少用一個元素的空間,約定入隊前測試尾指針在循環下加 1 後是否等於頭指針,若相等則認為隊滿;(最常用)   (3)、使用一個計數器記錄隊列中元素的總數。   對於第2個方案,必須犧牲一個元素的空間,那麼入隊的時候需要測試,循環意義下的加 1 操作可以描述為:   復制代碼 1     if (rear + 1 = MAXQSIZE) 2  3            rear = 0; 4  5      else 6  7           rear ++;  復制代碼 利用模運算可簡化為:   1 rear = (rear + 1)% MAXQSIZE   基本操作   復制代碼  1 #ifndef ___queue_Header_h  2 #define ___queue_Header_h  3 #include <stdio.h>  4 #include <stdlib.h>  5 #define MAX_SIZE 5  6   7 typedef struct{  8     int *base;  9     int rear;//如果隊列不空,指向隊尾元素的下一個位置 10     int front;//初始的時候指向表頭 11 } CirularQueue; 12  13 //初始化 14 void initQueue(CirularQueue *queue) 15 { 16     queue->base = (int *)malloc(MAX_SIZE*sizeof(int)); 17      18     if (NULL == queue->base) { 19         exit(0); 20     } 21      22     queue->front = queue->rear = 0; 23 } 復制代碼 求長度   復制代碼 //求長度 int getLength(CirularQueue queue) {     //這樣把所以的情況都考慮到了     return (queue.rear - queue.front + MAX_SIZE) % MAX_SIZE; } 復制代碼 第一種情況,長度的求法       第二種情況,長度的求法,利用模運算,兩個情況合二為一!       復制代碼 //入隊,先判滿 void insertQueue(CirularQueue *queue, int e) {     if ((queue->rear + 1) % MAX_SIZE == queue->front) {         puts("循環隊列是滿的!");     }     else     {         queue->base[queue->rear] = e;         queue->rear = (queue->rear + 1) % MAX_SIZE;     } } 復制代碼 如下時為滿,損失一個空間,不存儲元素。方便判滿       復制代碼  1 //出隊  2 void deleteQueue(CirularQueue *queue)  3 {  4     if (queue->front == queue->rear) {  5         puts("隊列是空的!");  6     }  7     else  8     {  9         queue->front = (queue->front + 1) % MAX_SIZE; 10     } 11 } 12  13 //遍歷 14 void traversal(CirularQueue queue) 15 { 16     int q = queue.front; 17      18     for (int i = 0; i < getLength(queue); i++) { 19         printf("循環隊列的第%d個元素為%d\n", i + 1, queue.base[q]); 20         q++; 21     } 22 } 23  24 #endif 復制代碼 測試   復制代碼  1 #include "Header.h"  2   3 int main(int argc, const char * argv[]) {  4     CirularQueue queue;  5     puts("循環隊列初始化:");  6     initQueue(&queue);  7       8     puts("循環隊列初始化後長度:");  9     printf("%d\n", getLength(queue)); 10      11     puts("循環隊列5個元素入隊,總長度為5,但是損失一個位置空間,實際存儲4個元素。先進先出原則:"); 12     puts("循環隊列元素0入隊"); 13     insertQueue(&queue, 0); 14     puts("循環隊列元素1入隊"); 15     insertQueue(&queue, 1); 16     puts("循環隊列元素2入隊"); 17     insertQueue(&queue, 2); 18     puts("循環隊列元素3入隊"); 19     insertQueue(&queue, 3); 20      21     puts("循環隊列元素遍歷:"); 22     traversal(queue); 23      24     puts("循環隊列元素繼續入隊,無法完成:"); 25     insertQueue(&queue, 4); 26      27     puts("循環隊列元素0出隊之後,先進先出原則:"); 28     deleteQueue(&queue); 29     traversal(queue); 30      31     puts("循環隊列元素1出隊之後,先進先出原則:"); 32     deleteQueue(&queue); 33     traversal(queue); 34      35     puts("循環隊列元素2出隊之後,先進先出原則:"); 36     deleteQueue(&queue); 37     traversal(queue); 38      39     puts("循環隊列元素3出隊之後,先進先出原則:"); 40     deleteQueue(&queue); 41     traversal(queue); 42      43     puts("4個元素全部刪除,循環隊列已經空了:"); 44     deleteQueue(&queue); 45      46     traversal(queue); 47      48     return 0; 49 } 復制代碼 結果;   循環隊列初始化:   循環隊列初始化後長度:   0   循環隊列5個元素入隊,總長度為5,但是損失一個位置空間,實際存儲4個元素。先進先出原則:   循環隊列元素0入隊   循環隊列元素1入隊   循環隊列元素2入隊   循環隊列元素3入隊   循環隊列元素遍歷:   循環隊列的第1個元素為0   循環隊列的第2個元素為1   循環隊列的第3個元素為2   循環隊列的第4個元素為3   循環隊列元素繼續入隊,無法完成:   循環隊列是滿的!   循環隊列元素0出隊之後,先進先出原則:   循環隊列的第1個元素為1   循環隊列的第2個元素為2   循環隊列的第3個元素為3   循環隊列元素1出隊之後,先進先出原則:   循環隊列的第1個元素為2   循環隊列的第2個元素為3   循環隊列元素2出隊之後,先進先出原則:   循環隊列的第1個元素為3   循環隊列元素3出隊之後,先進先出原則:   4個元素全部刪除,循環隊列已經空了:   隊列是空的!   Program ended with exit code: 0       小結:   若用戶需要循環隊列,那麼要設置隊列的最大長度,否則無法完成判斷空,如果用戶不知道最大長度是多少,那麼應該使用鏈隊。隊列在程序設計中和棧一樣,應用很多,未完待續。    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved