隊列的知識講解與基本實現(數據結構)

引言

中午在食堂打飯,真是一個令人頭疼的事情,去食堂的路上也總是步伐匆匆,爲什麼啊,這還用說,遲一點去,你就會知道什麼叫做人山人海了,在食堂排隊的時候,相比較學生來說,打飯阿姨畢竟是少數,在每個窗口都有人的時候,不免我們就得等待,直到前面的一個學生打完飯離開,後面排隊的人纔可以繼續向前走,直到輪到自己,別提多費勁了,但是秩序和規則卻是我們每個人都應該遵守的,也只能抱怨自己來的遲了

這種 “先進先出” 的例子就是我們所講的基本數據結構之一 ”隊列“

例子補充:用電腦的時候,有時候機器會處於疑似死機的狀態, 鼠標點什麼似乎都沒有用,雙擊任何快捷方式都不動,就當你失去耐心,打算reset的時候,突然它就像酒醒了一樣,把你剛纔點擊的所有操作全部按照順序執行了一遍,這其實是因爲操作系統中的多個程序隱需要通過一個通道輸出,而按照先後次序排隊等待造成的 ——《大話數據結構》

隊列的基本定義

定義:隊列是一種只允許在一段進行刪除操作,在另一端進行插入操作的線性表

允許插入的一段稱作隊尾 (rear),允許刪除的的一端稱爲隊頭 (front)

隊列的數據元素又叫做隊列元素,在隊列中插入一個隊列元素稱爲入隊,從隊列中刪除一個隊列元素稱爲出隊 ,也正是因爲隊列只允許在一段插入,另一端刪除,所以這也就是我們前面例子中體現出來的先進先出 (FIFO-first in first out) 的概念

補充:除此之外,還有的隊列叫做雙端隊列,也就是可以在表的兩邊進行插入和刪除操作的線性表

雙端隊列分類:

  1. 輸出受限的雙端隊列:刪除操作限制在表的一段進行,而插入操作允許早表的兩端進行
  2. 插入操作限制在表的一段進行,而刪除操作允許在表的兩端進行

隊列的抽象數據類型

#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <exception>
using namespace std;

// 用於檢查範圍的有效性
class outOfRange:public exception {        
public:    
    const char* what()const throw() {    
        return "ERROR! OUT OF RANGE.\n";
    } 
};  

// 用於檢查長度的有效性
class badSize:public exception {            
public:    
    const char* what()const throw() {
        return "ERROR! BAD SIZE.\n";
    }  
}; 

template <class T>
class Queue {
public:
    //判隊空 
    virtual bool empty() const = 0;
    //清空隊列 
    virtual void clear() = 0;
    //求隊列長度 
    virtual int size() const = 0;
    //入隊 
    virtual void enQueue(const T &x) = 0;
    //出隊 
    virtual T deQueue() = 0;
    //讀隊頭元素 
    virtual T getHead() const = 0;
    //虛析構函數 
    virtual ~Queue(){} 
};
#endif 

循環隊列

隊列作爲一個特殊的線性表,自然也有着順序以及鏈式存儲兩種方式,我們先來看看它的順序存儲方式——循環隊列

在隊列的順序存儲中,我們除了創建一個具有一定空間的數組空間外,還需要兩個指針,分別指向隊列的前端和微端,下面的代碼中,我們選擇將隊頭指針指向頭元素的前一個位置,隊尾指針指向隊尾元素(當然這不是唯一的方式,還可以將頭指針指向頭元素,隊尾指針指向隊尾元素的後一個位置,原理是基本一致的)

爲什麼要這麼做,並且爲什麼這種存儲我們叫做循環隊列?

我們一步步分析一下:

我們先按照我們一般的想法畫出隊列元素進出隊的過程,例如隊列元素出隊

這樣的設想,也就是根據我們前面食堂排隊的例子畫出來的,但是我們可以清晰的看到,當a0出隊後,a0後的元素全部需要前移,將空位補上,但我們在計算機中講究性能二字,如何可以提高出隊的性能呢?

循環隊列就這樣被設計出來了,我們如果不再限制隊頭一定在整個空間的最前面,我們的元素也就不需要集體移動了

問題一

這個時候我們就需要考慮這樣的問題了:

① 如何爲了解決只有一個元素的時候,隊頭和隊尾重合使得處理變得麻煩?

  • 這時我們前面提到的兩個指針就派上用場了(隊頭指針指向頭元素的前一個位置,隊尾指針指向隊尾元素)當頭尾指針相等的時候,代表是空隊列

問題二

但是有一個大問題出現了 !

如果前面有空閒的空間還好說,一旦頭元素前面沒有空間,我們的隊頭指針就指向到了數組之外,也就會出現數組越界問題,這該怎麼辦呢?

我們可以看到,雖然我們的表頭已經沒有了任何空間,但是表的後半部分還有空餘空間,這種現象我們稱作假溢出,打個比方,接近上課你緩緩走進教室,看到只有前排剩下了兩個位置,你總不會轉身就走吧,當然可以去前排坐,只有實在沒座位了,才考慮離開

我們可以做出這樣一種比較可行的方案

  • 我們只需要將這個隊列收尾連接起來,當後面的空間滿後,接着從前面空出來的空間中進隊,同樣的,我們的表頭指針也找到了可以指向的位置
  • 具體的連接方法,就是將date[0...maxSize] 的單元0認爲是maxSize - 1

問題三

我們剛纔也提到了,當表頭指針和表尾指針相等的時候就解決了空隊列的情況,但是在表滿的情況下,你會發現,同樣也滿足表頭表尾指針相等,那麼又如何解決這個問題呢?(我們給出三種可行的解決方案)

  • A:設置一個標誌變量flag,當front = rear的時,且flag = 0的時候爲空,若flag = 1 的時候爲隊列滿
  • B:設計一個計數器count統計當前隊列中的元素數量,count == 0 隊列空,count == maxsSize 隊列滿
  • C:保留一個存儲空間用於區分是否隊列已滿,也就是說,當一個還空閒一個單元時候,我們就認爲表已經滿了

我們重點講解 C 中的方法

我們根據這種方法可以總結出幾個條件的運算式

  • 隊列爲滿的條件:(rear+1) % MaxSize == front
  • 隊列爲空的條件:front == rear
  • 隊列中元素的個數:(rear- front + maxSize) % MaxSize
  • 入隊:rear = (rear + 1) % maxSize
  • 出隊:front = (front + 1) % maxSize

(一) 順序隊列的類型定義

#ifndef _SEQQUEUE_H_
#define _SEQQUEUE_H_
#include "Queue.h"

template <class T>
class seqQueue:public Queue<T> {
private:
    //指向存放元素的數組 
    T &data;
    //隊列的大小 
    int maxSize;
    //定義隊頭和隊尾指針 
    int front, rear;
    //擴大隊列空間 
    void resize();
public: 
    seqQueue(int initSize = 100);
    ~seqQueue() {delete []data;}
    //清空隊列 
    void clear() {front = rear = -1;}
    //判空
    bool empty() const {return front == rear;} 
    //判滿
    bool full() const {return (rear + 1) % maxSize == front;}
    //隊列長度
    int size() const {(rear- front + maxSize) % maxSize;}
    //入隊
    void enQueue(const T &x);
    //出隊 
    T deQueue();
    //取隊首元素
    T getHead() const; 
}; 

#endif 

(二) 初始化一個空隊列

template <class T>
seqQueue<T>::seqQueue(int initSize) {
    if (initSize <= 0) throw badSize();
    data = new T[initSize];
    maxSize = initSize;
    front = rear = -1; 
}

(三) 入隊

template <class T>
void seqQueue<T>::enQueue(const T &x) {
    //隊滿則擴容 
    if ((rear + 1) % maxSize == front) resize();
    //移動隊尾指針 
    rear = (rear + 1) % maxSize;
    //x 入隊 
    data[rear] = x;
}

(四) 出隊

template <class T>
T seqQueue<T>::deQueue() {
    //隊列爲空則拋出異常 
    if (empty()) throw outOfRange();
    //移動隊尾指針 
    front = (front + 1) % maxSize;
    //x入隊 
    return data[front];
}

(五) 取隊首元素

template <class T>
T seqQueue<T>::getHead() const {
    if (empty()) throw outOfRange();
    //返回隊首元素,不移動隊首指針 
    return data[(front + 1) % maxSize];
}

(六) 擴大隊列空間

template <class T>
void seqQueue<T>::resize() {
    T *p = data;
    data = new T[2 *maxSize];
    for(int i = 1; i < size(); ++i)
        //複製元素 
        data[i] = p[(front + i) % maxSize];
    //設置隊首和隊尾指針 
    front = 0; rear = size();
    maxSize *= 2;
    delete p;
}

鏈隊列

用鏈式存儲結構表示隊列我們叫做鏈隊列,用無頭結點的單鏈表表示隊列,表頭爲隊頭,表尾爲隊尾,需要兩個指針分別指向隊頭元素和隊尾元素,這種存儲結構的好處之一就是不會出現隊列滿的情況

(一) 順序隊列的類型定義

#ifndef _LINKQUEUE_H_
#define _LINKQUEUE_H_
#include <iostream>
#include "Queue.h"

template <class T>
class linkQueue:public Queue<T> {
private:
    struct node {
        T data;
        node *next;
        node (const T &x, node *N = NULL) {
            data = x;
            next = N;
        }
        node ():next(NULL){}
        ~node () {} 
    };
    node *front, *rear;
public: 
    linkQueue(){front = rear = NULL;};
    ~linkQueue() {clear();}
    //清空隊列 
    void clear();
    //判空
    bool empty() const {return front == NULL;} 
    //隊列長度
    int size() const;
    //入隊
    void enQueue(const T &x);
    //出隊 
    T deQueue();
    //取隊首元素
    T getHead() const; 
};

#endif 

(二) 清空隊列

template <class T>
void linkQueue<T>::clear() {
    node *p;
    //釋放隊列中所有節點 
    while(front != NULL) {
        p = front;
        front = front -> next;
        delete p;
    }
    //修改尾指針 
    rear = NULL;
} 

(三) 求隊列長度

template <class T>
int linkQueue<T>::size() const {
    node *p = front;
    int count = 0;
    while(p) {
        count++;
        p = p -> next;
    } 
    return count;
}

(四) 入隊

template <class T>
void linkQueue<T>::enQueue(const T &x) {
    if(rear == NULL) 
        front = rear = new node(x);
    else {
        rear -> next = new node(x);
        rear = rear -> next;
    }
}

(五) 出隊

template <class T>
T linkQueue<T>::deQueue() {
    //隊列爲空則拋出異常 
    if (empty()) throw outOfRange();
    node *p = front;
    //保存隊首元素 
    T value = front -> data;
    front = front -> next;
    if (front == NULL)
        rear = NULL;
    delete p;
    return value;
}

(六) 取隊首元素

template <class T>
T linkQueue<T>::getHead() const {
    if (empty()) throw outOfRange();
    return front -> data; 
}

結尾:

如果文章中有什麼不足,或者錯誤的地方,歡迎大家留言分享想法,感謝朋友們的支持!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公衆號

在這裏的我們素不相識,卻都在爲了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公衆號:理想二旬不止

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