數據結構之隊列分析與實現

隊列是一種十分常見的數據結構,它的應用範圍非常廣泛,例如在操作系統中調度算法、人工客服排隊序列等。隊列要求刪除元素的操作必須在隊列頭部,這個過程稱爲出隊(pop),而隊列插入元素的操作必鬚髮生在隊列尾部,這個過程稱爲進隊(push)。隊列不允許隨意訪問其中元素,能夠直接訪問的元素只有隊頭元素。

順序隊列。順序隊列需要或動態或靜態分配一塊連續的內存,設置隊頭指針front指向隊頭元素和隊尾指針rear指向隊尾指針的下一個元素。如下圖時代表內存地址爲1,2,3時是隊列元素,而4並不是。

這種結構的隊列由於入隊和出隊操作中,頭尾指針只增加不減小,致使出隊元素的空間永遠無法重新利用。雖然隊列中實際的元素個數遠遠小於向量空間的規模時,rear指針也可能已超越向量空間的上界。這種情況下或者當隊列已滿時進行進隊操作會導致溢出,進行進隊操作之前應當檢測隊尾指針是否溢出。這樣的隊列判空條件 front == rear。

鏈式隊列。因爲隊列內部元素不允許隨意訪問,只能在對頭或隊尾操作,所以如果使用鏈表實現隊列,那麼鏈表元素訪問不便的缺點也無關緊要了,更重要的是,使用鏈表實現隊列的話,理論上隊列大小隻取決於內存大小,並且出隊元素的空間可以被釋放重新利用,進隊元素即時申請內存。相較於順序結構隊列,容量以及空間利用率都大幅提高。鏈式隊列設置的隊頭指針front指向隊頭節點,隊尾指針rear指向隊尾節點。判空條件front或rear爲空(與順序結構隊列不同,當front==rear時隊列有一個元素)。

循環隊列。循環隊列是在順序結構隊列的基礎之上優化,充分利用存儲空間,並且減少了維護隊列結構的開銷。循環隊列在結構上與順序隊列沒有太大區別。不過當隊頭指針front或隊尾指針rear到達存儲邊界時,下一次移動並非繼續向前移動,而是移動至存儲空間的第一個位置。如下圖,若現在有一個元素進隊之後,rear將移動至1處。

在邏輯上可以把這塊區域看作是一塊環狀空間,使得隊列可以循環使用,隊列滿時會覆蓋隊頭元素,front向前移動一個單位。但是這樣會導致一個問題,當隊列爲空時有front==rear,當隊列滿時,也有front==rear。爲了避免這種情況的發生,我們通常的做法是在最大存儲空間(maxSize)的基礎之上添加一個空的存儲單元,比如上圖有九個存儲空間但是真正作爲隊列使用的maxSize只有八個,rear指向的單元永遠不會有隊列元素。這樣當front==rear時隊列爲空,當front==(rear + 1)%maxSize時隊列已滿,實時隊列大小爲(rear - front + maxSize) % maxSize。

 

基於鏈表的鏈式隊列

設計鏈式節點的結構

typedef struct Node {
    dataType data;  //數據域
    struct Node *next;  //指針域
} *list;

設計隊列結構

typedef struct {
    list front;  //隊頭節點
    list rear;  //隊尾節點
    int size;   //隊列大小
} queue;

相關操作

queue createQueue() {   //創建一個新的隊列
    queue res;
    res.front = res.rear = NULL;
    res.size = 0;
    return res;
}

bool isEmpty(queue Q) { //判斷一個隊列是否爲空
    return Q.size == 0;
}

dataType getFront(queue Q) {    //獲取隊頭元素
    return Q.front->data;
}

void pop(queue &Q) { //隊頭元素出隊
    list p = Q.front;
    Q.front = Q.front->next;
    Q.size--;
    free(p);
}

void push(queue &Q, dataType value) {    //元素進入隊尾
    list p = (list)malloc(sizeof(list));
    p->next = NULL;
    p->data = value;
    if(isEmpty(Q)) {    //若隊列爲空,前後隊頭隊尾均爲新添加元素
        Q.front = Q.rear = p;
    }
    else {  //隊列不爲空,移動隊尾
        Q.rear->next = p;
        Q.rear = p;
    }
    Q.size++;
}

int getSize(queue Q) {  //獲取隊列大小
    return Q.size;
}

 

動態分配的循環鏈表

#define dataType int    //隊列數據類型

typedef struct {
    dataType *start;
    int front;  //隊頭指針
    int rear;  //隊尾指針
    int maxSize;    //隊列最大存儲量
} circularQueue;

circularQueue createCircularQueue(int maxSize) {   //創建一個新的循環隊列,參數爲隊列最大存儲數量
    circularQueue res;
    res.start = (dataType *)malloc(sizeof(dataType) * (maxSize + 1));
    res.maxSize = maxSize;
    res.front = res.rear = 0;
    return res;
}

bool isEmpty(circularQueue Q) { //判斷一個循環隊列是否爲空
    return Q.front == Q.rear;
}

bool isFull(circularQueue Q) {  //判斷隊列是否已滿
    return ((Q.rear + 1) % (Q.maxSize + 1)) == Q.front;
}

dataType getFront(circularQueue Q) {    //獲取隊頭元素
    return Q.start[Q.front];
}

void pop(circularQueue &Q) { //隊頭元素出隊
    Q.front = (Q.front + 1) % (Q.maxSize + 1);
}

void push(circularQueue &Q, dataType value) {    //元素進入隊尾
    if(isFull(Q)) { //如果隊列已滿,隊頭元素將被覆蓋,隊頭指針需要移動
        pop(Q);
    }
    Q.start[Q.rear] = value;
    Q.rear = (Q.rear + 1) % (Q.maxSize + 1);
}

int getSize(circularQueue Q) {  //獲取隊列大小
    return (Q.rear - Q.front + Q.maxSize + 1) % (Q.maxSize + 1);
}

文件下載

基於鏈表的隊列.cpp

循環隊列.cpp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章