數(shù)據(jù)結(jié)構(gòu)——隊(duì)列

我們在使用手機(jī)的時候,偶爾都會碰到過卡住的時候,比如一個地方怎么點(diǎn)都沒有用,屏幕也卡住不顯示其他東西,但當(dāng)你把卡住的App關(guān)閉掉之后,手機(jī)的操作顯示就又恢復(fù)正常了,其實(shí)這就是因?yàn)椴僮飨到y(tǒng)中的各個程序的指令堆積在一起排隊(duì)執(zhí)行,而某一個App卡住的時候,大家都卡住了。

操作系統(tǒng)中是應(yīng)用了一種數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)剛才提到的先到先執(zhí)行的排隊(duì)功能,這就是隊(duì)列。

隊(duì)列的定義

隊(duì)列是一種只允許在一端進(jìn)行插入操作,而在另一端進(jìn)行刪除操作的線性表。

隊(duì)列是一種先進(jìn)先出(First In First Out)的線性表,簡稱FIFO。允許插入的一端為隊(duì)尾,允許刪除的一端稱為隊(duì)頭。假設(shè)隊(duì)列 q = (q1,q2,q3,....qn),那么我們一般定義q1就是隊(duì)頭,而qn自然為隊(duì)尾了。這樣我們在刪除操作時,就從q1開始,而插入操作時則從qn開始。這也比較符合我們的生活習(xí)慣,我們在排隊(duì)的時候,就是先到的人先出列,而晚到的人就在隊(duì)尾排隊(duì)。

循環(huán)隊(duì)列

以前的文章,我寫了線性表有順序存儲結(jié)構(gòu)和鏈?zhǔn)酱鎯Y(jié)構(gòu),而同樣,隊(duì)列作為一種特殊的線性表,自然也存在這兩種存儲方式。首先我們先來看隊(duì)列的順序存儲結(jié)構(gòu)。

隊(duì)列順序存儲結(jié)構(gòu)的不足

和線性表的順序存儲結(jié)構(gòu)的缺點(diǎn)一樣,隊(duì)列的若是采用常規(guī)的順序存儲結(jié)構(gòu),那么它在插入和刪除時,每個元素都要依次向前或向后移動位置,此時的時間復(fù)雜度為O(n)。而當(dāng)隊(duì)列中隊(duì)頭之前的位置空出來,而隊(duì)尾的元素已滿時,明明在隊(duì)頭之前可能還有空間,但是按照順序存儲結(jié)構(gòu)的判斷,此時已經(jīng)不能插入數(shù)據(jù),再插入數(shù)據(jù)的話,整個數(shù)組就會溢出,而這種之前有空位,卻插入到后面溢出位置的做法,我們稱為 假溢出 。

循環(huán)隊(duì)列的定義

所以為了解決這種假溢出的辦法就是后面滿了,就再從頭開始,尋找之前空出來的空間,把數(shù)據(jù)存儲進(jìn)去,也就是頭尾相接的循環(huán)。我們把隊(duì)列這種頭尾相接的順序存儲結(jié)構(gòu)稱為循環(huán)隊(duì)列。

循環(huán)隊(duì)列的順序存儲結(jié)構(gòu)代碼如下:


#define MAXSIZE 10   //循環(huán)隊(duì)列的最大存儲空間

/**
 *  函數(shù)運(yùn)行狀態(tài)代碼
 */
#define SUCCESS 1
#define ERROR   0

typedef int Status ; //函數(shù)的運(yùn)行狀態(tài) 假設(shè)為int類型

typedef int QElemType; //QElemType的類型根據(jù)實(shí)際情況而定,這里假設(shè)為int

/**
 *  定義一個循環(huán)隊(duì)列
 */
typedef struct{
    
    QElemType data[MAXSIZE];
    
    int front; //循環(huán)隊(duì)列的頭指針
    
    int rear;  //循環(huán)隊(duì)列的尾指針
    
}sqQueue;

循環(huán)隊(duì)列的初始化代碼如下:


/**
 *  初始化一個循環(huán)隊(duì)列
 *
 *  @param Q 循環(huán)隊(duì)列的線性表
 *
 *  @return Status
 */

Status InitQueue (sqQueue *Q)
{
    Q->front = 0;
    Q->rear = 0;
    
    return SUCCESS;
}

我們來實(shí)現(xiàn)求一個循環(huán)隊(duì)列的長度的功能,其實(shí)很簡單,只要返回尾指針與頭指針相減的數(shù)據(jù)就可以,當(dāng)然,這里我們要考慮當(dāng)循環(huán)了一遍之后頭尾交換位置的情況


/**
 *  求循環(huán)隊(duì)列的隊(duì)列長度
 *
 *  @param Q 循環(huán)隊(duì)列的線性表
 *
 *  @return length
 */
int QueueLength (sqQueue Q)
{
    return (Q.rear - Q.front +MAXSIZE) %MAXSIZE;
}

跟線性表一樣,我們一般要完成插入和刪除功能的代碼,而實(shí)現(xiàn)部分如下:

循環(huán)隊(duì)列的入隊(duì)列操作代碼:


/**
 *  循環(huán)隊(duì)列的入隊(duì)操作
 *
 *  @param Q 循環(huán)隊(duì)列的線性表
 *  @param e 將要插入的數(shù)據(jù)
 *
 *  @return Sataus
 */
Status EnQueue (sqQueue * Q ,QElemType e)
{
    if ((Q->rear + 1) %MAXSIZE == Q->front) {    //判斷隊(duì)列是否滿
        return ERROR;
    }
    
    Q->data[Q->rear] = e;              //將元素e賦值給隊(duì)尾
    Q->rear = (Q->rear + 1) %MAXSIZE;  //將rear尾指針后移一位
                                       //若到隊(duì)尾則轉(zhuǎn)到數(shù)組頭部
    return SUCCESS;
}

循環(huán)隊(duì)列的出隊(duì)列的操作代碼 :


/**
 *  循環(huán)隊(duì)列的出隊(duì)列操作
 *
 *  @param Q 循環(huán)隊(duì)列的線性表
 *  @param e 存儲隊(duì)頭數(shù)據(jù)的元素
 *
 *  @return Status
 */
Status DeQueue (sqQueue * Q, QElemType *e)
{
    if (Q->front == Q->rear) {               //判斷隊(duì)列是否為空
        return ERROR;
    }
    *e = Q->data[Q->front];                  //將隊(duì)頭元素賦值給e
    Q->front = (Q->front + 1) %MAXSIZE;      //front指針后移一位
    
    return SUCCESS;
}

從上面的代碼,我們能夠分析出,單是順序存儲,若不是循環(huán)隊(duì)列,算法的時間性能是不高的,但是循環(huán)隊(duì)列又面臨著數(shù)組可能會溢出的問題,所以我們還需要研究一下不需要擔(dān)心隊(duì)列長度的鏈?zhǔn)酱鎯Y(jié)構(gòu)。

隊(duì)列的鏈?zhǔn)酱鎯Y(jié)構(gòu)及實(shí)現(xiàn)

隊(duì)列的鏈?zhǔn)酱鎯Y(jié)構(gòu),其實(shí)就是線性表的單鏈表,只不過它只能尾進(jìn)頭出而已,我們把它簡稱為鏈隊(duì)列。為了操作上的方便,我們將隊(duì)列的頭指針指向鏈隊(duì)列的頭結(jié)點(diǎn),而尾指針指向終點(diǎn)結(jié)點(diǎn)。

空隊(duì)列時,front和rear都指向頭結(jié)點(diǎn)。

鏈隊(duì)列的結(jié)構(gòu)為:


/**
 *  結(jié)點(diǎn)結(jié)構(gòu)
 */
typedef struct QNode
{
    QElemType data;
    struct QNode *next;
    
}QNode, *QueuePtr;

/**
 *  隊(duì)列的鏈表結(jié)構(gòu)
 */
typedef struct
{
    QueuePtr front, rear;    //隊(duì)頭隊(duì)尾指針
    
}LinkQueue;

鏈隊(duì)列的入隊(duì)操作:


/**
 *  插入元素e為鏈隊(duì)列的新的隊(duì)尾元素
 *
 *  @param Q 鏈隊(duì)列
 *  @param e 將要插入的元素e
 *
 *  @return Status
 */
Status EnLinkQueue (LinkQueue * Q, QElemType e)
{
    QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
    if (!s) {                    //存儲分配失敗
        return ERROR;
    }
    
    s->data = e;
    s->next = NULL;
    Q->rear->next = s;      //把擁有元素e的新節(jié)點(diǎn)s賦值給原隊(duì)尾結(jié)點(diǎn)的后繼
    
    Q->rear = s;            //把當(dāng)前的s設(shè)置為隊(duì)尾的結(jié)點(diǎn),rear指向s
    return SUCCESS;
}

鏈隊(duì)列的出隊(duì)操作,其實(shí)就是頭結(jié)點(diǎn)的后繼結(jié)點(diǎn)出隊(duì),將頭結(jié)點(diǎn)的后繼改成它后面的結(jié)點(diǎn),之后再釋放要刪除元素的內(nèi)存,若鏈表除了頭結(jié)點(diǎn)之外只剩下一個元素時,則要將rear指向頭結(jié)點(diǎn)。


/**
 *  若隊(duì)列不空,刪除鏈隊(duì)列的隊(duì)頭元素,并用e返回其值
 *
 *  @param Q 鏈隊(duì)列
 *  @param e 刪除的元素的數(shù)據(jù)
 *
 *  @return Status
 */
Status DeLinkeQueue (LinkQueue *Q, QElemType *e)
{
    QueuePtr p;
    if (Q->front == Q->rear) {
        return ERROR;
    }
    
    p = Q->front->next;             //將欲刪除的隊(duì)頭結(jié)點(diǎn)暫存給p
    *e = p->data;                   //將欲刪除的隊(duì)頭結(jié)點(diǎn)賦值給e
    Q->front->next = p->next;       //將原隊(duì)頭的結(jié)點(diǎn)后繼p->next賦值給隊(duì)頭結(jié)點(diǎn)的后繼
    
    if (Q->rear == p) {             //若隊(duì)頭是隊(duì)尾,則刪除后繼后將rear指向頭結(jié)點(diǎn)
        Q->rear = Q->front;
    }
    free(p);                        //釋放p
    return SUCCESS;
}

循環(huán)隊(duì)列和鏈隊(duì)列的比較

循環(huán)隊(duì)列和鏈隊(duì)列的比較可以從兩個方面來比較,首先從時間上,其實(shí)它們的基本操作都是常數(shù)時間,時間復(fù)雜度都為O(1),不過循環(huán)隊(duì)列是事先申請好空間的,而鏈隊(duì)列是即時申請空間的所以鏈隊(duì)列的每次申請和釋放操作都會帶來一定的性能消耗和時間開銷。

從空間上來說,循環(huán)隊(duì)列必須有一個固定的長度,所以就有了存儲元素個數(shù)和空間的浪費(fèi)的問題,而鏈隊(duì)列則不存在這個問題。

總得來說,在可以確定隊(duì)列長度最大值的情況下,建議用循環(huán)隊(duì)列,如果你無法預(yù)估隊(duì)列的長度時,則用鏈隊(duì)列。

總結(jié)

我們在這里的總結(jié),將棧和隊(duì)列拿來比較。

  • 棧(stack)是限定進(jìn)在表尾進(jìn)行插入和刪除操作的線性表
  • 隊(duì)列(Queue)是只允許在一端進(jìn)行插入操作,而在另一端進(jìn)行刪除操作的線性表。

他們均可以用線性表的順序存儲結(jié)構(gòu)來實(shí)現(xiàn),但都存在著順序存儲的一些弊端,因此它們各自有各自的技巧來解決這個問題。

對于棧來說,如果存儲的數(shù)據(jù)類型相同的棧,則可以用數(shù)組的兩端作棧底的方法來讓兩個棧共享數(shù)據(jù),這就可以最大化的利用數(shù)組的空間。

對于隊(duì)列來說,為了避免數(shù)組插入和刪除時需要移動數(shù)據(jù),于是就引入了循環(huán)隊(duì)列,使得隊(duì)頭和隊(duì)尾可以在數(shù)組中循環(huán)變化。解決了移動數(shù)據(jù)的時間損耗,使得本來插入和刪除的時間復(fù)雜度從O(n)變成了O(1)。

它們也都可以通過鏈?zhǔn)酱鎯Y(jié)構(gòu)來實(shí)現(xiàn),實(shí)現(xiàn)原則上與線性表基本相同。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容