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

棧與列隊(duì)

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

棧的定義

棧(stack)是限定僅在表尾進(jìn)行插入和刪除操作的線性表

允許插入和刪除的一端稱為棧頂(top),另一端稱為棧底(bottom),不含任何數(shù)據(jù)元素的棧稱為空棧。棧又稱為后進(jìn)先出(LastIn First Out)的線性表,簡(jiǎn)稱LIFO結(jié)構(gòu)

棧的插入操作,叫作進(jìn)棧,也稱壓棧、入棧,如下圖所示:

56import.png

棧的刪除操作,叫作出棧,也有的叫作彈棧,如下圖所示:

57import.png

棧的抽象數(shù)據(jù)類(lèi)型

對(duì)于棧來(lái)講,理論上線性表的操作特性它都具備,可由于它的特殊性,所以針對(duì)它在操作上會(huì)有些變化,特別是插入和刪除,push和pop操作,即壓棧和出棧

ADT 棧(stack)
Data
    同線性表。元素具有相同的類(lèi)型,相鄰元素具有前驅(qū)和后繼關(guān)系。
Operation
    Initstack(*S); 初始化操作,建立一個(gè)空棧S。
    DestroyStack(*S); 若棧存在,則銷(xiāo)毀它。
    ClearStack(*S); 將棧清空。
    StackEmpty(S); 若棧為空,返回true;否則返回false。
    GetTop(S,*e); 若棧存在且非空,用e返回S的棧頂元素。
    Push(*S,e); 若棧S存在,插入新元素e到棧S中并成為棧頂元素。又稱:進(jìn)棧,壓棧,入棧。
    Pop(*S,*e); 刪除棧S中棧頂元素,并用e返回其值。又稱:出棧,彈棧。
    StackLength(S); 返回棧S的元素個(gè)數(shù)。
endADT

由于棧本身就是一個(gè)線性表,所以同樣具有線性表的順序存儲(chǔ)結(jié)構(gòu)和鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)

棧的順序存儲(chǔ)結(jié)構(gòu)

既然棧是線性表的特例,那么棧的順序存儲(chǔ)其實(shí)也是線性表順序存儲(chǔ)的簡(jiǎn)化,我們簡(jiǎn)稱為順序棧。線性表是用數(shù)組來(lái)實(shí)現(xiàn)的,那么想想看,對(duì)于棧這種只能一頭插入、刪除的線性表來(lái)說(shuō),用數(shù)組哪一端作為棧頂和棧底比較好?

通常使用下標(biāo)為0的一端作為棧底,因?yàn)槭自囟即嬖跅5?,變化最小,所以讓它作為棧底。定義一個(gè)top變量來(lái)指示棧頂元素在數(shù)組中的位置,若存儲(chǔ)棧的長(zhǎng)度為StackSize,則棧頂位置top必須小于StackSize。當(dāng)棧存在一個(gè)元素時(shí),top等于0,通常把空棧的判定條件定為top等于?1

棧的結(jié)構(gòu)定義

typedef int SElemType;      /* SElemType類(lèi)型根據(jù)實(shí)際情況而定,這里假設(shè)為int */
typedef struct
{    
  SElemType data[MAXSIZE];        
  int top;                /* 用于棧頂指針 */ 
}SqStack;

若現(xiàn)在有一個(gè)棧,StackSize是5,則棧普通情況、空棧和棧滿的情況示意圖如下:

59import.png

進(jìn)棧操作

棧的插入,即入棧操作,入下圖:

60import.png

入棧操作Push

/* 插入元素e為新的棧頂元素 */
Status Push(SqStack *S,SElemType e)
{
    if(S->top == MAXSIZE -1) /* 棧滿 */
    {
        return ERROR;
    }
    S->top++;                /* 棧頂指針增加一 */
    S->data[S->top]=e;  /* 將新插入元素賦值給棧頂空間 */
    return OK;
}

出棧操作

出棧操作Pop

/* 若棧不空,則刪除S的棧頂元素,用e返回其值,   并返回OK;否則返回ERROR */
Status Pop(SqStack *S, SElemType *e)  
{  
    if (S->top == -1)  // ???        return ERROR;  
    *e = S->data[S->top];    /* 將要?jiǎng)h除的棧頂元素賦值給e */ 
    S->top--;                /* 棧頂指針減一 */
    return OK;  
}

兩棧共享空間

其實(shí)棧的順序存儲(chǔ)還是很方便的,因?yàn)樗辉试S棧頂進(jìn)出元素,所以不存在線性表插入和刪除需要移動(dòng)元素的問(wèn)題,但是也存在一個(gè)缺陷,就是必須先確定數(shù)組存儲(chǔ)空間大小,萬(wàn)一不夠用,就需要使用編程手段進(jìn)行擴(kuò)容,很麻煩。

用一個(gè)數(shù)組來(lái)存儲(chǔ)兩個(gè)棧,數(shù)組有兩個(gè)端點(diǎn),兩個(gè)棧有兩個(gè)棧底,讓一個(gè)棧的棧底為數(shù)組的始端,即下標(biāo)為0處,另一個(gè)棧為數(shù)組的末端,即下標(biāo)為數(shù)組長(zhǎng)度n-1處。兩個(gè)棧如果增加元素,就是兩端點(diǎn)向中間延伸

61import.png

關(guān)鍵思路:它們是在數(shù)組的兩端,向中間靠攏,top1和top2是棧1和棧2的棧頂指針,只要它們倆不見(jiàn)面,兩個(gè)棧就可以一直使用。

當(dāng)棧1為空時(shí),top1等于-1;當(dāng)棧2為空時(shí),top2等于n。若棧2是空棧,棧1的top1等于n-1時(shí),就是棧1滿了。反之,當(dāng)棧1為空,top2等于0時(shí),棧2滿。但是更多數(shù)情況下是兩個(gè)棧見(jiàn)面之時(shí),也就是兩個(gè)指針相差1,即top1+1=top2為棧滿

兩棧共享空間

/*兩棧共享空間結(jié)構(gòu) */
typedef struct
{    
   SElemType data[MAXSIZE];    
   int top1;    /* 棧1棧頂指針 */    
   int top2;    /* 棧2棧頂指針 */
} SqDoubleStack;

對(duì)于兩棧共享空間的push方法,除了要插入元素值參數(shù)外,還需要有一個(gè)判斷是棧1還是棧2的棧號(hào)參數(shù)stackNumber。

插入元素

/* 插入元素e為新的棧頂元素 */
Status Push(SqDoubleStack *S, SElemType e, int stackNumber)
{    
    /* 棧已滿,不能再push新元素了 */    
    if (S->top1 + 1 == S->top2)            
        return ERROR;    
    /* 棧1有元素進(jìn)棧 */    
    if (stackNumber == 1)                  
        /* 若棧1則先top1+1后給數(shù)組元素賦值 */        
        S->data[++S->top1] = e;        
    /* 棧2有元素進(jìn)棧 */    
    else if (stackNumber == 2)             
        /* 若棧2則先top2-1后給數(shù)組元素賦值 */        
        S->data[--S->top2] = e;        
    return OK;
}

因?yàn)殚_(kāi)始我們就已經(jīng)判斷了是否有棧滿的情況,所以后面的top1+1和top2-1是不需要擔(dān)心溢出問(wèn)題的。

彈出元素

兩棧共享空間的pop方法,判斷棧1棧2的參數(shù)stackNumber即可

/* 若棧不空,則刪除S的棧頂元素,用e返回其值,并返回OK;否則返回ERROR */
Status Pop(SqDoubleStack *S,SElemType *e,int stackNumber)
{ 
    if (stackNumber==1) 
    {
        if (S->top1==-1) 
           return ERROR; /* 說(shuō)明棧1已經(jīng)是空棧,溢出 */
           *e=S->data[S->top1--]; /* 將棧1的棧頂元素出棧 */
    }
    else if (stackNumber==2)
    { 
         if (S->top2==MAXSIZE) 
             return ERROR; /* 說(shuō)明棧2已經(jīng)是空棧,溢出 */
             *e=S->data[S->top2++]; /* 將棧2的棧頂元素出棧 */
    }
    return OK;
}

使用這樣的數(shù)據(jù)結(jié)構(gòu),通常都是兩個(gè)棧的空間需求有相反關(guān)系時(shí),也就是一個(gè)棧增長(zhǎng)另外一個(gè)??s短的情況。這樣使用兩棧共享空間存儲(chǔ)方法才有較大的意義,否則兩個(gè)棧都在不停的增長(zhǎng),那么很快就會(huì)因棧滿而溢出。

棧的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)

棧的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu),簡(jiǎn)稱為鏈棧

由于單鏈表有頭指針,而棧頂指針也是必須的,所以比較好的辦法是把棧頂放在單鏈表的頭部。另外,都已經(jīng)有了棧頂在頭部了,單鏈表中比較常用的頭結(jié)點(diǎn)也就失去了意義,通常對(duì)于鏈棧來(lái)說(shuō),是不需要頭結(jié)點(diǎn)的。

62import.png

對(duì)于鏈棧來(lái)說(shuō),基本不存在棧滿的情況,除非內(nèi)存已經(jīng)沒(méi)有可以使用的空間,如果真的發(fā)生,那此時(shí)的計(jì)算機(jī)操作系統(tǒng)已經(jīng)面臨死機(jī)崩潰的情況,而不是這個(gè)鏈棧是否溢出的問(wèn)題。但對(duì)于空棧來(lái)說(shuō),鏈表原定義是頭指針指向空,那么鏈棧的空其實(shí)就是top=NULL的時(shí)候。

鏈棧的結(jié)構(gòu)

/* 鏈棧結(jié)構(gòu) */
typedef struct StackNode
{
    SElemType data;
    struct StackNode *next;
} StackNode,*LinkStackPtr;

typedef struct LinkStack
{
     LinkStackPtr top;
     int count;
} LinkStack;

棧的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)——進(jìn)棧操作

對(duì)于鏈棧的進(jìn)棧push操作,假設(shè)元素值為e的新結(jié)點(diǎn)是s,top為棧頂指針,如下圖

63import.png
/* 插入元素e為新的棧頂元素 */
Status Push(LinkStack *S,SElemType e)
{
    LinkStackPtr s=(LinkStackPtr)malloc(sizeof(StackNode));
    s->data=e;
    s->next=S->top;    /* 把當(dāng)前的棧頂元素賦值給新結(jié)點(diǎn)的直接后繼,見(jiàn)圖中1 */
    S->top=s;         /* 將新的結(jié)點(diǎn)s賦值給棧頂指針,見(jiàn)圖中2 */
    S->count++;
    return OK;
}

棧的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)——出棧操作

假設(shè)變量p用來(lái)存儲(chǔ)要?jiǎng)h除的棧頂結(jié)點(diǎn),將棧頂指針下移一位,最后釋放p即可

64import.png
/* 若棧不空,則刪除S的棧頂元素,用e返回其值,并返回OK;否則返回ERROR */
Status Pop(LinkStack *S,SElemType *e)
{ 
    LinkStackPtr p;
    if(StackEmpty(*S))
            return ERROR;
    *e=S->top->data;
    p=S->top;                /* 將棧頂結(jié)點(diǎn)賦值給p,見(jiàn)圖中③ */
    S->top=S->top->next;    /* 使得棧頂指針下移一位,指向后一結(jié)點(diǎn),見(jiàn)圖中④ */
    free(p);               /* 釋放結(jié)點(diǎn)p */        
    S->count--;
    return OK;
}

鏈棧的進(jìn)棧push和出棧pop沒(méi)有任何循環(huán)操作,時(shí)間復(fù)雜度均為O(1)。

順序棧與鏈棧對(duì)比

1、它們時(shí)間復(fù)雜度上均為O(1)。

2、對(duì)于空間性能,順序棧需要事先確定一個(gè)固定的長(zhǎng)度,可能會(huì)存在內(nèi)存空間浪費(fèi)的問(wèn)題,但它的優(yōu)勢(shì)是存取時(shí)定位很方便,而鏈棧則要求每個(gè)元素都有指針域,這同時(shí)也增加了一些內(nèi)存開(kāi)銷(xiāo),但對(duì)于棧的長(zhǎng)度無(wú)限制

3、如果棧的使用過(guò)程中元素變化不可預(yù)料,有時(shí)很小,有時(shí)非常大,那么最好是用鏈棧,如果它的變化在可控范圍內(nèi),建議使用順序棧

棧的作用

棧的引入簡(jiǎn)化了程序設(shè)計(jì)的問(wèn)題,劃分了不同關(guān)注層次,使得思考范圍縮小,更加聚焦于要解決的問(wèn)題核心。反之,像數(shù)組等,因?yàn)橐稚⒕θタ紤]數(shù)組的下標(biāo)增減等細(xì)節(jié)問(wèn)題,反而掩蓋了問(wèn)題的本質(zhì)。

對(duì)于一些高級(jí)的語(yǔ)言,比如:Java,OC等都有對(duì)棧結(jié)構(gòu)的封裝,可以不需要關(guān)注它的實(shí)現(xiàn)細(xì)節(jié),就可以直接使用stack的push和pop方法,非常方便。

棧的應(yīng)用-遞歸

棧有一個(gè)很重要的應(yīng)用:在程序設(shè)計(jì)語(yǔ)言中實(shí)現(xiàn)了遞歸。遞歸中的一個(gè)結(jié)點(diǎn)例子:斐波那契數(shù)列。

斐波那契數(shù)列實(shí)現(xiàn)

如果兔子在出生兩個(gè)月后就有繁殖能力,一對(duì)兔子每個(gè)月能生出一對(duì)小兔子。假設(shè)所有的兔都不死,那么一年后可以繁殖多少對(duì)兔子?

分析

第一個(gè)月兔子沒(méi)有繁殖能力,所以還是一對(duì);第二個(gè)月也還是一對(duì),兩個(gè)月后,生下一對(duì)兔子,小兔子總數(shù)為2;三個(gè)月后,老兔子又生下一對(duì),因?yàn)樾⊥米幽壳斑€沒(méi)有繁殖能力,所以現(xiàn)在為3對(duì)兔子,這樣一次類(lèi)推,如下圖:

屏幕快照 2018-09-05 下午6.59.40.png

表中數(shù)字1,1,2,3,5,8,13...構(gòu)成了一個(gè)序列,而且又明顯的特點(diǎn):前面相鄰兩項(xiàng)只和構(gòu)成后一項(xiàng)

66import.png

編號(hào)①的一對(duì)兔子經(jīng)過(guò)六個(gè)月就變成8對(duì)兔子,數(shù)學(xué)函數(shù)來(lái)定義就是:

屏幕快照 2018-09-05 下午7.08.52.png

打印出前40位的斐波那契數(shù)列數(shù)。

#include "stdio.h"

int Fbi(int i)  /* 斐波那契的遞歸函數(shù) */
{
    if( i < 2 )
        return i == 0 ? 0 : 1;  
    return Fbi(i - 1) + Fbi(i - 2);  /* 這里Fbi就是函數(shù)自己,等于在調(diào)用自己 */
}  

int main()
{
    int i;
    int a[40];  
    printf("迭代顯示斐波那契數(shù)列:\n");
    a[0]=0;
    a[1]=1;
    printf("%d ",a[0]);  
    printf("%d ",a[1]);  
    for(i = 2;i < 40;i++)  
    { 
        a[i] = a[i-1] + a[i-2];  
        printf("%d ",a[i]);  
    } 
    printf("\n");

    printf("遞歸顯示斐波那契數(shù)列:\n");
    for(i = 0;i < 40;i++)  
        printf("%d ", Fbi(i));  
    return 0;
}

遞歸定義

把一個(gè)直接調(diào)用自己或通過(guò)一系列的調(diào)用語(yǔ)句間接地調(diào)用自己的函數(shù),稱做遞歸函數(shù)。當(dāng)然,寫(xiě)遞歸程序最怕的就是陷入永不結(jié)束的無(wú)窮遞歸中,所以,每個(gè)遞歸定義必須至少有一個(gè)條件,滿足時(shí)遞歸不再進(jìn)行。

迭代 遞歸
循環(huán)結(jié)構(gòu) 選擇結(jié)構(gòu)
不需要反復(fù)調(diào)用函數(shù)和占用額外的內(nèi)存 使程序的結(jié)構(gòu)更清晰、更簡(jiǎn)潔、更容易讓人理解,從而減少讀懂代碼的時(shí)間。但是大量的遞歸調(diào)用會(huì)建立函數(shù)的副本,會(huì)耗費(fèi)大量的時(shí)間和內(nèi)存

在前行階段,對(duì)于每一層遞歸,函數(shù)的局部變量、參數(shù)值以及返回地址都被壓入棧中。在退回階段,位于棧頂?shù)木植孔兞?、參?shù)值和返回地址被彈出,用于返回調(diào)用層次中執(zhí)行代碼的其余部分,也就是恢復(fù)了調(diào)用的狀態(tài)

隊(duì)列的定義

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

隊(duì)列是一種先進(jìn)先出(First In First Out)的線性表,簡(jiǎn)稱FIFO。允許插入的一端稱為隊(duì)尾,允許刪除的一端稱為隊(duì)頭。假設(shè)隊(duì)列是q=(a1,a2,...,an),那么那么a1就是隊(duì)頭元素,而an是隊(duì)尾元素。刪除時(shí),總是從a1開(kāi)始,而插入時(shí),列在最后。

80import.png

隊(duì)列的抽象數(shù)據(jù)類(lèi)型

ADT 隊(duì)列(Queue)
Data    
    同線性表。元素具有相同的類(lèi)型,相鄰元素具有前驅(qū)和后繼關(guān)系。
Operation    
    InitQueue(*Q):    初始化操作,建立一個(gè)空隊(duì)列Q。    
    DestroyQueue(*Q): 若隊(duì)列Q存在,則銷(xiāo)毀它。    
    ClearQueue(*Q):   將隊(duì)列Q清空。    
    QueueEmpty(Q):    若隊(duì)列Q為空,返回true,否則返回false。    
    GetHead(Q, *e):   若隊(duì)列Q存在且非空,用e返回隊(duì)列Q的隊(duì)頭元素。    
    EnQueue(*Q, e):   若隊(duì)列Q存在,插入新元素e到隊(duì)列Q中并成為隊(duì)尾元素。    
    DeQueue(*Q, *e):  刪除隊(duì)列Q中隊(duì)頭元素,并用e返回其值。    
    QueueLength(Q):   返回隊(duì)列Q的元素個(gè)數(shù)
endADT

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

線性表有順序存儲(chǔ)和鏈?zhǔn)酱鎯?chǔ),棧式線性表,所以有這兩種存儲(chǔ)方式,同樣,列隊(duì)作為一種特殊的線性表,也同樣存在這兩種存儲(chǔ)方式。先看列隊(duì)的順序存儲(chǔ)結(jié)構(gòu)。

隊(duì)列順序存儲(chǔ)的不足

假設(shè)一個(gè)隊(duì)列有n個(gè)元素,則順序存儲(chǔ)的隊(duì)列需建立一個(gè)大于n的數(shù)組,并把隊(duì)列的所有元素存儲(chǔ)在數(shù)組的前n個(gè)單元,數(shù)組下標(biāo)為0的一端即是隊(duì)頭。所謂的入隊(duì)列操作就是在隊(duì)尾追加一個(gè)元素,不需要移動(dòng)任何元素,時(shí)間復(fù)雜度為O(1),如下圖:

81import.png

與棧不同的是,隊(duì)列元素的出列是在隊(duì)頭,即下標(biāo)為0的位置,隊(duì)列中的所有元素都得向前移動(dòng),以保證隊(duì)列的隊(duì)頭,也就是下標(biāo)為0的位置不為空,此時(shí)時(shí)間復(fù)雜度為O(n),如下圖:

82import.png

可細(xì)想一下,為什么出列隊(duì)時(shí)一定要移動(dòng)全部元素?如果不去限制列隊(duì)的元素必須存儲(chǔ)在數(shù)組的前n個(gè)單元這一條件,那么出隊(duì)的性能就會(huì)大大增加,也就是說(shuō),隊(duì)頭不需要一定在下標(biāo)為0的位置。

為了避免當(dāng)只有一個(gè)元素時(shí),隊(duì)頭和隊(duì)尾重合使處理變得麻煩,所以引入兩個(gè)指針,front指針指向隊(duì)頭元素,rear指針指向隊(duì)尾元素的下一個(gè)位置,當(dāng)front等于rear時(shí),此隊(duì)列不是還剩一個(gè)元素,而是空隊(duì)列。

假設(shè)是長(zhǎng)度為5的數(shù)組,初始狀態(tài),front與rear指針均指向下標(biāo)為0的位置。然后入隊(duì)a1、a2、a3、a4,front指針依然指向下標(biāo)為0位置,而rear指針指向下標(biāo)為4的位置

83import.png

出隊(duì)a1,a2,front指針指向下標(biāo)為2的位置,rear不變,再入隊(duì)a5,front指針不變,rear指針移動(dòng)到數(shù)組之外,如下圖

84import.png

而且問(wèn)題還不止如此,假設(shè)這個(gè)列隊(duì)的總個(gè)數(shù)不超過(guò)5個(gè),但是目前繼續(xù)接著入隊(duì),因?yàn)閿?shù)組末尾元素已經(jīng)被占用,再向后加,就會(huì)產(chǎn)生數(shù)組越界。而實(shí)際上,我們列隊(duì)的下標(biāo)0和1的地方還是空閑,這是一種“假溢出”。

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

為了解決假溢出問(wèn)題,當(dāng)后面滿了,就再?gòu)念^開(kāi)始,也就是頭尾相接的循環(huán)。隊(duì)列的這種頭尾相接的順序存儲(chǔ)結(jié)構(gòu)稱為循環(huán)隊(duì)列。

繼續(xù)上面的例子,rear可以改為指向下標(biāo)為0的位置,這樣就不會(huì)造成指針指向不明的問(wèn)題,如下圖:

85import.png

接著入隊(duì)a6,將它放置于下標(biāo)為0處,rear指針指向下標(biāo)為1處,如左圖。再入隊(duì)a7,則rear指針就與front指針重合,同時(shí)指向下標(biāo)為2的位置,如下圖

86import.png

此時(shí)問(wèn)題又來(lái)了,因?yàn)榭樟嘘?duì)的時(shí)候front等于rear,現(xiàn)在當(dāng)列隊(duì)滿了,也是front等于rear,那么如何判斷此時(shí)的隊(duì)列究竟是空還是滿呢?

辦法一:設(shè)置一個(gè)標(biāo)志變量flag,當(dāng)front==rear,且flag=0時(shí)為隊(duì)列空,當(dāng)front==rear,且flag=1時(shí)為隊(duì)列滿。

辦法二:當(dāng)隊(duì)列空時(shí),條件就是front=rear,當(dāng)隊(duì)列滿時(shí),保留一個(gè)元素空間,如下圖。也就是說(shuō),隊(duì)列滿時(shí),數(shù)組中還有一個(gè)空閑單元,就認(rèn)為此隊(duì)列已經(jīng)滿了,即不允許上圖右圖情況出現(xiàn)

87import.png

由于rear可能比f(wàn)ront大,也可能比f(wàn)ront小,所以盡管它們只相差一個(gè)位置時(shí)就是滿的情況,但也可能是相差整整一圈, 若隊(duì)列的最大尺寸為QueueSize,隊(duì)列滿的條件是(rear+1)%QueueSize==front
(取?!?”的目的就是為了整合rear與front大小為一個(gè)問(wèn)題)。

比如上面例子中:

QueueSize = 5,當(dāng)front=0,rear=4,(4+0) % 5=0,此時(shí)列隊(duì)滿;
又比如front=2,而rear=1。(1+1)%5=2,此時(shí)隊(duì)列也是滿的;
而front=2,rear=0,(0+1)%5=1,1≠2,此時(shí)隊(duì)列并沒(méi)有滿。

另外,當(dāng)rear>front時(shí),隊(duì)列的長(zhǎng)度為rear-front。當(dāng)rear<front時(shí),隊(duì)列長(zhǎng)度分為兩段,一段是QueueSize-front,另一段是0+rear,加在一起,隊(duì)列長(zhǎng)度為rear-front+Queue。因此通用的計(jì)算隊(duì)列長(zhǎng)度公式為:

(rear-front + QueueSize) % QueueSize

循環(huán)隊(duì)列的順序存儲(chǔ)結(jié)構(gòu)

typedef int QElemType;
/* 循環(huán)隊(duì)列的順序存儲(chǔ)結(jié)構(gòu) */
typedef struct
{
    QElemType data[MAXSIZE];
    int front;      /* 頭指針 */
    int rear;       /* 尾指針,若隊(duì)列不空,指向隊(duì)列尾元素的下一個(gè)位置 */
}SqQueue;

初始化

/* 初始化一個(gè)空隊(duì)列Q */
Status InitQueue(SqQueue *Q)
{
    Q->front=0;
    Q->rear=0;
    return  OK;
}

求隊(duì)列長(zhǎng)度

/* 返回Q的元素個(gè)數(shù),也就是隊(duì)列的當(dāng)前長(zhǎng)度 */
int QueueLength(SqQueue Q)
{
    return  (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}

入隊(duì)列操作

/* 若隊(duì)列未滿,則插入元素e為Q新的隊(duì)尾元素 */
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指針向后移一位置, */
                                /* 若到最后則轉(zhuǎn)到數(shù)組頭部 */
    return  OK;
}

出隊(duì)列操作

/* 若隊(duì)列不空,則刪除Q中隊(duì)頭元素,用e返回其值 */
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指針向后移一位置, */
                                    /* 若到最后則轉(zhuǎn)到數(shù)組頭部 */
    return  OK;
}

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

隊(duì)列的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)就是線性表的單鏈表,只不過(guò)它只能尾進(jìn)頭出,簡(jiǎn)稱為鏈隊(duì)列。

將隊(duì)頭指針指向鏈隊(duì)列的頭結(jié)點(diǎn),隊(duì)尾指針指向終端結(jié)點(diǎn)

88import.png

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

89import.png

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

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

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

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

入隊(duì)操作

入隊(duì)操作時(shí)就是在鏈表尾部插入結(jié)點(diǎn)

90import.png
/* 插入元素e為Q的新的隊(duì)尾元素 */
Status EnQueue(LinkQueue *Q,QElemType e)
{
    QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
    if(!s) /* 存儲(chǔ)分配失敗 */
        exit(OVERFLOW);
    s->data=e;
    s->next=NULL;
    Q->rear->next=s;    /* 把擁有元素e的新結(jié)點(diǎn)s賦值給原隊(duì)尾結(jié)點(diǎn)的后繼,見(jiàn)圖中1 */
    Q->rear=s;      /* 把當(dāng)前的s設(shè)置為隊(duì)尾結(jié)點(diǎn),rear指向s,見(jiàn)圖中2 */
    return OK;
}

出隊(duì)操作

出隊(duì)操作時(shí),就是頭結(jié)點(diǎn)的后繼結(jié)點(diǎn)出隊(duì),將頭結(jié)點(diǎn)的后繼改為它后面的結(jié)點(diǎn),若鏈表除頭結(jié)點(diǎn)外只剩一個(gè)元素時(shí),則需將rear指向頭結(jié)點(diǎn)

91import.png
/* 若隊(duì)列不空,刪除Q的隊(duì)頭元素,用e返回其值,并返回OK,否則返回ERROR */
Status DeQueue(LinkQueue *Q,QElemType *e)
{
    QueuePtr p;
    if(Q->front==Q->rear)
        return ERROR;
    p=Q->front->next;       /* 將欲刪除的隊(duì)頭結(jié)點(diǎn)暫存給p,見(jiàn)圖中1 */
    *e=p->data;             /* 將欲刪除的隊(duì)頭結(jié)點(diǎn)的值賦值給e */
    Q->front->next=p->next;/* 將原隊(duì)頭結(jié)點(diǎn)的后繼p->next賦值給頭結(jié)點(diǎn)后繼,見(jiàn)圖中2 */
    if(Q->rear==p)      /* 若隊(duì)頭就是隊(duì)尾,則刪除后將rear指向頭結(jié)點(diǎn),見(jiàn)圖中3 */
        Q->rear=Q->front;
    free(p);
    return OK;
}

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

時(shí)間上,基本操作都為O(1),不過(guò)循環(huán)隊(duì)列是事先申請(qǐng)好空間,使用期間不釋放,而對(duì)于鏈隊(duì)列,每次申請(qǐng)和釋放結(jié)點(diǎn)也會(huì)存在一些時(shí)間開(kāi)銷(xiāo),如果入隊(duì)出隊(duì)頻繁,則兩者還是有細(xì)微差異。

空間上,循環(huán)隊(duì)列必須有一個(gè)固定的長(zhǎng)度,所以就有了存儲(chǔ)元素個(gè)數(shù)和空間浪費(fèi)的問(wèn)題。而鏈隊(duì)列不存在這個(gè)問(wèn)題,盡管它需要一個(gè)指針域,會(huì)產(chǎn)生一些空間上的開(kāi)銷(xiāo),但也可以接受。所以在空間上,鏈隊(duì)列更加靈活。

在可以確定隊(duì)列長(zhǎng)度最大值的情況下,建議用循環(huán)隊(duì)列,如果無(wú)法預(yù)估隊(duì)列的長(zhǎng)度時(shí),則用鏈隊(duì)列

參考

《大話數(shù)據(jù)結(jié)構(gòu)》

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

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

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