數據結構(11)隊列之循環隊列

前言

昨天說到在順序隊列中,如果按照“入隊則尾指針加一,出隊則頭指針加一”的方式實現入隊出隊操作,就可能出現尾指針已越界,無法插入元素;隊列實際上卻仍有空間的“假溢出”現象。如圖

img_1

爲了充分利用空間,我們可以再讓尾指針循環回來,尋找插入的位置。這樣,隊列的這種頭尾相接的順序存儲結構就被稱爲循環隊列。

爲了方便理解,書上常常將循環隊列畫成一個環狀的結構,但是實際上它仍然是線性的。

img_2

img_3

這樣,在循環隊列中,判斷隊列已滿的條件就從“查看rear值是否大於等於隊列最大容量”,變成了“查看rear值是否與fount值相等”。

img_4

循環隊列的實現

按照現在的設計,我們進行插入操作(新元素入隊)時,需要比較rear值與fount值是否相等,如果相等說明隊列已滿,無法插入。但回想我們在初始化隊列時,把fount值和rear值都初始化爲0了。現在rear值與fount值相等就有兩種情況:第一是隊列是空的,可以插入元素;第二是隊列已滿,無法插入元素。如何區分這兩種狀態呢?主要有以下幾種方法:

  • 設置一個flag變量標誌隊列的狀態,當flag爲0時,說明隊列未滿;當flag爲1時,說明隊列已滿。
  • 與上一個類似,設置一個變量來記錄隊列內的元素個數,當元素個數小於隊列最大容量時,說明隊列爲滿;元素個數大於等於隊列最大容量時,說明隊列已滿。(也可以不特別用變量來記錄,而是用獲取隊列內元素個數的方法來獲得當前的元素個數,主要看自己是怎麼實現這個方法的。)
  • 一個比較特別的方法是:少用一個元素空間,當隊列還有最後一個空間時,我們就認爲它已經滿了。什麼意思呢?即設隊列最大容量爲m,當隊列內有m-1個元素時,就認爲它滿了。這時候會有三種情形:

img_5
在這種前提下,當(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)。

img_6

循環隊列求當前元素個數

循環隊列在求當前元素個數(即長度)時,同樣有三種情形:

img_7

第一種是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

img_8

全部代碼

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章