數據結構(11)隊列之循環隊列
前言
昨天說到在順序隊列中,如果按照“入隊則尾指針加一,出隊則頭指針加一”的方式實現入隊出隊操作,就可能出現尾指針已越界,無法插入元素;隊列實際上卻仍有空間的“假溢出”現象。如圖
爲了充分利用空間,我們可以再讓尾指針循環回來,尋找插入的位置。這樣,隊列的這種頭尾相接的順序存儲結構就被稱爲循環隊列。
爲了方便理解,書上常常將循環隊列畫成一個環狀的結構,但是實際上它仍然是線性的。
這樣,在循環隊列中,判斷隊列已滿的條件就從“查看rear值是否大於等於隊列最大容量”,變成了“查看rear值是否與fount值相等”。
循環隊列的實現
按照現在的設計,我們進行插入操作(新元素入隊)時,需要比較rear值與fount值是否相等,如果相等說明隊列已滿,無法插入。但回想我們在初始化隊列時,把fount值和rear值都初始化爲0了。現在rear值與fount值相等就有兩種情況:第一是隊列是空的,可以插入元素;第二是隊列已滿,無法插入元素。如何區分這兩種狀態呢?主要有以下幾種方法:
- 設置一個flag變量標誌隊列的狀態,當flag爲0時,說明隊列未滿;當flag爲1時,說明隊列已滿。
- 與上一個類似,設置一個變量來記錄隊列內的元素個數,當元素個數小於隊列最大容量時,說明隊列爲滿;元素個數大於等於隊列最大容量時,說明隊列已滿。(也可以不特別用變量來記錄,而是用獲取隊列內元素個數的方法來獲得當前的元素個數,主要看自己是怎麼實現這個方法的。)
- 一個比較特別的方法是:少用一個元素空間,當隊列還有最後一個空間時,我們就認爲它已經滿了。什麼意思呢?即設隊列最大容量爲m,當隊列內有m-1個元素時,就認爲它滿了。這時候會有三種情形:
在這種前提下,當(rear == fount)時,說明隊列是空的。那麼判定隊滿的條件是什麼呢?假如只考慮到 1 和 2 兩種情況,很明顯就是( (rear+1) == fount);但是還有 3 這種情況,這時候fount和rear相差了整整一圈,需要解決的問題是:怎樣讓rear循環回來?
我首先想到的辦法是:當rear值等於數組最大下標時,再把它設爲0就行了。當然因爲涉及到與fount的比較,是設爲0還是-1,自增後設置還是設置後自增等等問題還需要詳細考慮,能不能行也還沒有實際去做,這裏就只記錄下思路。
現在說下正常的方法:首先需要知道,當數A與某個數B求餘時(A % B),得到的結果一定是在 0~(B-1) 這個範圍內。利用求餘運算的特性,我們不讓rear單純地自增,而是自增後與隊列最大容量求餘( (rear + 1) % MAXSIZE)。這樣,當rear小於數組最大下標(MAXSIZE-1)時,rear仍是自增的;當rear值等於數組最大下標(MAXSIZE-1)時,rear就被設成了0; rear的值始終在 0~(MAXSIZE-1)範圍內循環。那麼,判斷隊滿的條件就是:((rear+1)%MAXSIZE == fount)。
循環隊列求當前元素個數
循環隊列在求當前元素個數(即長度)時,同樣有三種情形:
第一種是rear > fount,第二種是 rear < fount,第三種則是 rear == fount。
毫無疑問,如果是第一第三種情況時,只需要(rear - fount)就可以得到當前元素個數了。但是在第二種情況中,(rear - fount)得到的是負值,由於此時fount和rear相差了整整一圈,實際上應該是( (MAXSIZE+rear) - fount)。
現在使用if判斷情況然後調用不同的算式固然能得到答案,但有沒有能統一三種情況的方法呢?求餘運算又登場了。假如我們都使用(MAXSIZE + rear - fount)這個算式,可以發現在第一第三種情況中,算式值只是多了一個MAXSIZE值而已,我們讓它與MAXSIZE求餘,所得的結果就是(rear - fount)。而在第二種情況中,由於算式值小於MAXSIZE,所得的結果仍是(MAXSIZE + rear - fount)。
這樣,通用的計算公式就是:(rear - fount + MAXSIZE)%MAXSIZE
全部代碼
SeqQueue.h
#ifndef SeqQueue_h
#define SeqQueue_h
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define ElemType int
#define MAXSIZE 8
typedef struct Queue{
ElemType *base;
int fount;
int rear;
}Queue;
//初始化
void InitQueue(Queue *Q);
//入隊
void EnQueue(Queue *Q,ElemType x);
//出隊
void DeQueue(Queue *Q);
//展示
void ShowQueue(Queue *Q);
//獲取隊首元素
void GetHead(Queue *Q,ElemType *x);
//求長度
int GetLength(Queue *Q);
//清除
void CleatQueue(Queue *Q);
//摧毀
void DestoryQueue(Queue *Q);
#endif /* SeqQueue_h */
SeqQueue.c
#include "SeqQueue.h"
//初始化
void InitQueue(Queue *Q){
//開闢空間
Q->base = (ElemType *)malloc(sizeof(ElemType)*MAXSIZE);
assert(Q->base != NULL);
//隊頭指針和隊尾指針指向0下標
Q->fount = Q->rear = 0;
}
//入隊
void EnQueue(Queue *Q,ElemType x){
//判斷隊列是否滿
if ((Q->rear + 1)%MAXSIZE == Q->fount) {
printf("隊列已滿\n");
return;
}
Q->base[Q->rear] = x;
Q->rear = (Q->rear+1)%MAXSIZE;
}
//出隊
void DeQueue(Queue *Q){
//判斷隊列是否空
if (Q->fount == Q->rear) {
printf("隊列已空\n");
return;
}
Q->fount = (Q->fount+1)%MAXSIZE;
}
//展示
void ShowQueue(Queue *Q){
for (int i = Q->fount; i != Q->rear;) {
printf("%4d",Q->base[i]);
i = (i+1)%MAXSIZE;
}
printf("\n");
}
//獲取隊首元素
void GetHead(Queue *Q,ElemType *x){
//判斷隊列是否空
if (Q->fount == Q->rear) {
printf("隊列已空\n");
return;
}
*x = Q->base[Q->fount];
}
//求長度
int GetLength(Queue *Q){
return (MAXSIZE + Q->rear - Q->fount) % MAXSIZE;
}
//清除
void CleatQueue(Queue *Q){
Q->fount = Q->rear = 0;
}
//摧毀
void DestoryQueue(Queue *Q){
free(Q);
Q->base = NULL;
}
Main.c
#include "SeqQueue.h"
int main(int argc, const char * argv[]) {
Queue Q;
InitQueue(&Q);
//入隊
for (int i = 0; i < 7; i ++) {
EnQueue(&Q, i);
}
ShowQueue(&Q);
//出隊
DeQueue(&Q);
EnQueue(&Q, 10);
ShowQueue(&Q);
DeQueue(&Q);
EnQueue(&Q, 20);
ShowQueue(&Q);
int len = GetLength(&Q);
printf("len = %d\n",len);
return 0;
}