09.迴圈隊列與鏈隊列,09迴圈隊列
一、隊列與迴圈隊列1.隊列(1)隊列(queue)是只允許在一端進行插入操作,而在另一端進行刪除操作的線性表。隊列是一種先進先出(Fiirst In First Out)的線性表,簡稱FIFO。允許插入的一端稱為隊尾,允許刪除的一端稱為隊頭。 從隊列的定義可知,隊列的入隊操作,其實就是在隊尾追加一個元素,不需要移動任何元素,因此時間複雜度為O(1)。隊列的刪除操作,與棧不同的是,隊列元素的出列是在隊頭,即小標為0的位置,若要刪除一個元素的話,需要移動隊列的所有元素,因此事件複雜度為O(n)。(2)front/rear指標:為了避免當只有一個元素時,隊尾和隊頭重合使處理變得麻煩,所有引入兩個指標,front指標指向隊頭元素,rear指標指向隊尾元素的下一個位置,這樣當front等於rear時,隊列為空白(空隊列)而不是隊列還剩下一個元素。
2.隊列的抽象資料類型ADT 隊列Data 同線性表。元素具有相同的類型,相鄰元素具有前驅和後繼關係。Operation InitQueue(*Q): 初始化操作,建立一個空隊列Q. DestoryQueue(*Q):若隊列Q存在,則銷毀它。 ClearQueue(*Q):將隊列Q清空 GetHead(Q,*e):若隊列Q存在且非空,用e返回隊列Q的隊頭元素。 EnQueue(*Q,e):若隊列Q存在且非空,插入新元素e到隊列Q中並稱為隊尾元素。 DeQueue(*Q,*e):刪除隊列Q中隊頭元素,並用e返回其值 QueueLength(Q):返回隊列Q的元素個數endADT3.迴圈隊列 (1)定義:隊列中頭尾相接的順序儲存結構稱為迴圈隊列,用於解決"假溢出"問題。(2)隊滿條件: 一般情況,當front=rear時,隊列可能為空白隊列也可以為滿隊列。所以我們假設,當front==rear時隊列為空白;當隊列滿時,數組還有一個空閑空間。由於rear可能比front大,也可以比front小。我們這樣定義,當隊列滿足條件"(rear+1)%QueueSize==front"時,我們就認為隊列已滿(QueueSize為隊列最大儲存容量)。(3)隊列長度公式 當rear>front時,隊列的長度為rear-front;當rear<front時,隊列的長度為(QueueSize-front)+(0+rear),其中font、rear、QueueSize均為數組下標。經過推導的計算隊列公式: (rear-front+QueueSize)%QueueSize(4)迴圈隊列的順序儲存結構typedef int QElemTypetypedef struct{ QElemType data[MAXSIZE]; int front; //隊頭指標 int rear; //隊尾指標,若隊列不空,指向隊尾元素的下一個位置}SqQueue;(5)迴圈隊列的相關操作A.初始化一個迴圈隊列QStatus InitQueue(SqQueue *Q){ Q->front=0; Q->rear=0; return OK;}B.計算迴圈隊列的長度/*返回Q的元素個數,即隊列的當前長度*/int QueueLength(SqQueue Q){ return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;}C.迴圈隊列插入操作實現:若隊列未滿,則插入元素e為新的隊尾元素Status EnQueue(SqQueue *Q,QElemType e){ if((Q->rear+1)%MAXSIZE == Q->front) //判斷棧滿((rear+1)%QueueSize==front) return ERROR; Q->data[Q->rear]=e; Q->rear=(Q->rear+1)%MAXSIZE; //rear指標向後移一位,若到最後則轉到數組頭部 return OK;}D.迴圈隊列刪除操作實現:若隊列不空,則刪除Q中隊頭元素。用e返回其值Status EnQueue(SqQueue *Q,QElemType *e){ if(Q->rear==Q->front) return ERROR; //隊列為空白條件:rear=front *e=Q->data[Q->front]; //將隊頭元素賦值給e Q->front=(Q->front+1)%MAXSIZE; //front指標向後移一位置,若到最後則轉到數組頭部 return OK;}總結:單是順序儲存,若不是迴圈隊列,演算法的時間效能是不高的,但迴圈隊列又面臨著數組可能會溢出的問題,所以我們下節引出隊列的鏈式儲存結構。二、隊列的鏈式儲存結構1.鏈隊列:隊列的鏈式儲存結構,其實就是線性表的單鏈表,只不過它只能尾進頭出,也稱鏈隊列。為了方便操作,隊頭指標指向鏈隊列的頭結點,隊尾指標指向終端結點。當隊頭指標front和隊尾指標rear都指向頭結點時,隊列為空白。
2.鏈隊列結構typedef int QElemType/*結點結構*/typedef struct QNode{ QElemType data; //資料域 struct QNode *next; //指標域}QNode,*Queueptr;/*隊列的鏈表結構*/typedef struct{ Queueptr front,rear; //隊頭、隊尾指標}LinkQueue;3.鏈隊列的入隊操作演算法:實質為鏈表尾插入結點,尾指標指向新結點a.為新結點s開闢一段空間,並判斷是否儲存分配成功;b.將插入元素存到新結點s的資料域,並初始化s的指標域;c.把擁有元素e新結點s賦值給原隊列結點的後繼;d.把當前的s設定為隊尾結點,rear指向s
實現:插入元素e為Q的新隊尾元素
Status EnQueue(LinkQueue *Q,QElemType e){ QueuePtr s=(QueuePtr)malloc(sizeof(QNode)); //為新結點s開闢一段空間 if(!s) //儲存分配失敗 exit(OVERFLOW); s->data=e; //將元素e儲存到新結點s的資料域 s->next=NULL;//初始化新結點的指標域 Q->rear->next=s; //把擁有元素e新結點s賦值給原隊列結點的後繼 Q->rear=s; //把當前的s設定為隊尾結點,rear指向s}
4.鏈隊列的刪除操作演算法:實質是頭結點的後繼結點出隊,將頭結點的後繼改為他後面的結點。若鏈表除頭結點外,只剩下一個元素時,則需將rear指向頭結點。a.定義一個QueuePtr結點p,用於暫存欲刪除的結點Q->front-next;b.將欲刪除結點資料域資料賦值給e;c.將欲刪除結點的後繼結點(p->next)賦值給頭隊頭結點的後繼(Q->front->next)d.判定若隊頭是隊尾,則刪除後將rear指向頭結點,最後再釋放欲刪除結點p.
實現:若隊列不空,刪除Q的隊頭元素,用e返回其值,並返回OK,否則返回ERROR
Status EnQueue(LinkQueue *Q,QElemType e){ QueuePtr p; if(Q->front==Q->rear) return ERROR; //隊列為空白 p=Q->front->next; //將隊頭指標指向的頭結點的後繼結點(欲刪除的結點)暫存給p *e=p->data; //將欲刪除結點的資料域資料賦值給e Q->front->next=p->next; //將元隊頭結點後繼p->next賦值給隊頭結點的後繼 if(Q->rear==p) //若隊頭是隊尾,則刪除後將rear指向頭結點 Q-rear=Q->front; free(p); return OK;}
三、迴圈隊列與鏈隊列效能分析1.迴圈隊列與鏈隊列基本操作時間複雜度均為O(1);2.迴圈隊列事先申請好空間,使用期間不釋放;鏈隊列,無需事先申請空間但是每次申請和釋放結點會存在一些事件開銷;3.迴圈隊列必須有一個固定的長度,可能存在空間浪費;鏈隊列需要一個指標域,會產生一些空間上的開銷,所以在可以確定隊列長度最大值的情況下建議用迴圈隊列。