數據結構(四)隊列
隊列
隊列的定義及基本運算
棧是一種後進先出的數據結構,在實際問題中還經常使用一種“先進先出”的數據結構: 即插入在表一端進行,而刪除在表的另一端進行,將這種數據結構稱為隊或隊列,把允許插入的一端叫隊尾(rear) ,把允許刪除的一端叫隊頭(front)。
隊列的存儲實現及運算實現
與線性表、棧類似,隊列也有順序存儲和鏈式存儲兩種存儲方法。
(1) InitQueue(&Q):初始化操作。設置一個空隊列。
(2) IsEmpty(Q):判空操作。若隊列為空,則返回 TRUE,否則返回 FALSE。
(3) IsFull(Q):判滿操作。若隊列為滿,則返回 TRUE,否則返回 FALSE。
(4) EnterQueue(&Q,x):進隊操作。在隊列 Q 的隊尾插入 x。操作成 功,返回值為 TRUE,否則返回值為 FALSE。
(5) DeleteQueue(&Q,&x):出隊操作。使隊列 Q 的隊頭元素出隊,並 用 x 帶回其值。操作成功,返回值為 TRUE,否則返回值為 FALSE。
(6) GetHead(Q,&x):取隊頭元素操作。用 x 取得隊頭元素的值。操 作成功,返回 TRUE,否則返回值為 FALSE。
(7) ClearQueue(&Q):隊列置空操作。將隊列 Q 置為空隊列。
(8) DestroyQueue(&Q): 隊列銷毀操作。釋放隊列的空間。
1.順序隊列
循環隊列的類型定義如下:
#define MAXQSIZE 100 //大隊列長度
typedef struct {
QElemType *base; //動態分配存儲空間
int front; //頭指針,若隊列不空,指向隊列頭元素
int rear; //尾指針,若隊列不空,指向隊列尾元素的下一個位置
} SqQueue;
(1)入隊:
int EnQueue (SqQueue &Q, QElemType e) {
if((Q.rear+1)%MAXQSIZE == Q.front) return ERROR;
Q.base[Q.rear] = e;
Q.rear = (Q.rear+1) % MAXQSIZE;
return OK;
}
(2)出隊:
int DeQueue (SqQueue &Q, QElemType &e) {
if (Q.front = = Q.rear) return ERROR;
e = Q.base[Q.front];
Q.front = (Q.front+1) % MAXQSIZE;
return OK;
}
(3)求循環隊列元素個數:
int QueueLength(SqQueue Q){
return (Q.rear-Q.front+MAXQSIZE) %MAXQSIZE;
}
2.鏈隊列
鏈式存儲的隊稱為鏈隊列。和鏈棧類似,用單鏈表來實現鏈隊列,根據隊的先進先出原 則,為了操作上的方便,分別需要一個頭指針和尾指針。隊頭指針始終指向頭結點,隊尾指針指向當前最後一個元素。空的鏈隊列的隊頭指針和隊尾指針均指 向頭結點。
鏈隊列的形式描述如下:
typedef struct QNode { // 結點類型
QElemType data;
struct QNode *next;
} QNode, *QueuePtr;
typedef struct { //鏈隊列類型
QueuePtr front; //隊頭指針
QueuePtr rear; //隊尾指針
} LinkQueue;
定義一個指向鏈隊列的指針:LinkQueue Q;
下面是鏈隊列的基本運算的實現。
(1)初始化
int InitQueue(LinkQueue * Q) { /* 將 Q 初始化為一個空的鏈隊列 */ Q->front=(LinkQueueNode *)malloc(sizeof(LinkQueueNode)); if(Q->front!=NULL) { Q->rear=Q->front; Q->front->next=NULL; return(TRUE); } else return(FALSE); /* 溢出!*/ }
(2)入隊
int EnQueue (LinkQueue &Q, QElemType e) {
QNode *p;
p = (QNode *)malloc(sizeof(QNode));
p->data = e;
p->next = NULL;
Q.rear->next = p; Q
.rear = p;
return OK;
}
(3)出隊
int DeQueue (LinkQueue &Q, QElemType &e) {
if (Q.front == Q.rear) return ERROR; //隊空,出隊失敗
p = Q.front->next;
e = p->data; //隊頭元素放 e 中
Q.front->next = p->next;
if(Q.rear==p) Q.rear= Q.front; //只有一個元素時,此時還要修改隊尾指針
free (p);
return OK;
}
3.除了棧和隊列之外,還有一種限定性數據結構是雙端隊列。
(1)雙端隊列:可以在雙端進行插入和刪除操作的線性表。
(2)輸入受限的雙端隊列:線性表的兩端都可以輸出數據元素,但是只能在一端輸入數 據元素。
(3)輸出受限的雙端隊列:線性表的兩端都可以輸入數據元素,但是只能在一端輸出數 據元素。
1. 順序隊列的假溢出現象
隊列的一種順序存儲稱為順序隊列。與順序棧類似,在隊列的順序存儲結構中,用一組地址連續的存儲單元依次存放從隊頭到隊尾的元素,如一維數組 Queue[MAXSIZE]。
由於隊列中隊頭和隊尾的位置都是動態變化的,因此需要附設兩個指針 front 和 rear。
front:指示隊頭元素在數組中的位置;
rear:指示真實隊尾元素相鄰的下一個位置。
初始化隊列時,令 front = rear =0;入隊時,直接將新元素送入尾指針 rear 所指的單元, 然後尾指針增 1;出隊時,直接取出隊頭指針 front 所指的元素,然後頭指針增 1。顯然,在 非空順序隊列中,隊頭指針始終指向當前的隊頭元素,而隊尾指針始終指向真正隊尾元素後 面的單元。當 rear==MAXSIZE 時,認為隊滿。但此時不一定是真的隊滿,因為隨著部分元 素的出隊,數組前面會出現一些空單元。由於只能在隊尾入隊,使得上述 空單元無法使用。把這種現象稱為假溢出,真正隊滿的條件是 rear - front=MAXSIZE 。
2. 循環隊列
為了解決假溢出現象並使得隊列空間得到充分利用,一個較巧妙的辦法是將順序隊列的數組看成一個環狀的空間,即規定最後一個單元的後繼為第一個單元,我們形象地稱之為循環隊列。假設隊列數組為 Queue[MAXSIZE],當 rear+1=MAXSIZE 時,令 rear=0,即可求得 最後一個單元 Queue[MAXSIZE-1]的後繼:Queue[0]。更簡便的辦法是通過數學中的取模(求 余)運算來實現:rear=(rear+1)mod MAXSIZE,顯然,當 rear+1=MAXSIZE 時,rear=0, 同樣可求得最後一個單元 Queue[MAXSIZE-1]的後繼:Queue[0]。所以,借助於取模(求余) 運算,可以自動實現隊尾指針、隊頭指針的循環變化。進隊操作時,隊尾指針的變化是:rear= (rear+1)mod MAXSIZE ;而出隊操作時,隊頭指針的變化是:front=(front+1)mod MAXSIZE。 下圖給出了循環隊列的幾種情況。
【循環隊列判空判滿問題】
與一般的非空順序隊列相同,在非空循環隊列中,隊頭指針始終指向當前的隊頭元素,而隊尾指針始終指向真正隊尾元素後面的單元。在下圖 (c)所示 循環隊列中,隊列頭元素是 e3,隊列尾元素是 e5,當 e6、e7和 e8相繼入隊後,隊列空間均被占滿,如上圖 (b)所示, 此時隊尾指針追上隊頭指針,所以有:front =rear。反之,若 e3、e4 和 e5相繼從上圖 (c)的 隊列中刪除,則得到空隊列,如上圖 (a)所示,此時隊頭指針追上隊尾指針,所以也存在關 系式:front = rear。可見,只憑 front = rear 無法判別隊列的狀態是“空”還是“滿”。
有兩種處理方法:
一種方法是少用一個元素空間。當隊尾指針所指向的空單元 的後繼單元是隊頭元素所在的單元時,則停止入隊。這樣一來,隊尾指針永遠追不上隊頭指 針,所以隊滿時不會有 front =rear。現在隊列“滿”的條件為(rear+1)mod MAXSIZE=front。 判隊空的條件不變,仍為 rear=front。
另一種是增設一個標誌量的方法,以區別隊列是“空” 還是“滿”。主要介紹損失一個存儲空間以區分隊列空與滿的方法。
循環隊列的類型定義
#define MAXSIZE 50 /*隊列的最大長度*/ typedef struct { QueueElementType element[MAXSIZE]; /* 隊列的元素空間*/ int front; /*頭指針指示器*/ int rear ; /*尾指針指示器*/ }SeqQueue;
循環隊列的基本操作
(1) 初始化操作
void InitQueue(SeqQueue * Q) { /* 將*Q 初始化為一個空的循環隊列 */ Q->front=Q->rear=0; }
(2) 入隊操作
int EnterQueue(SeqQueue *Q, QueueElementType x) { /*將元素 x 入隊*/ if((Q->rear+1)%MAXSIZE==Q->front) /*隊列已經滿了*/ return(FALSE); Q->element[Q->rear]=x; Q->rear=(Q->rear+1)%MAXSIZE; /* 重新設置隊尾指針 */ return(TRUE); /操作成功/ }
(3)出隊操作
int DeleteQueue(SeqQueue *Q, QueueElementType * x) { /*刪除隊列的隊頭元素,用 x 返回其值*/ if(Q->front==Q->rear) /*隊列為空*/ return(FALSE); *x=Q->element[Q->front]; Q->front=(Q->front+1)%MAXSIZE; /*重新設置隊頭指針*/ return(TRUE); /*操作成功*/ }
這裏采用了第一種處理假溢出問題的方法。如果采用第二種方法,則需要設置一個標誌 量 tag。初始化操作即產生一個空的循環隊列,此時 Q->front = Q->rear=0,tag=0;當空 的循環隊列中有第一個元素入隊時,則 tag=1,表示循環隊列非空;當 tag=1 且 Q->front=Q->rear 時,表示隊滿。
數據結構(四)隊列