資料結構—棧和佇列

張俊紅發表於2018-07-20

前言

本章節開始資料結構第二篇,棧和佇列:
棧:

  • 棧的儲存結構
  • 棧的基本操作

佇列:

  • 佇列的儲存結構
  • 佇列的基本操作

我們把類似於彈夾那種先進後出的資料結構稱為棧,棧是限定僅在表尾進行插入和刪除操作的線性表,我們把允許插入和刪除的一端稱為棧頂,另一端稱為棧底,不含任何資料元素的棧稱為空棧,棧又稱後進後出的線性表,簡稱LIFO結構。

棧首先是一個線性表,也就是說,棧元素具有線性關係,即前驅後繼關係,只不過它是一種特殊的線性表而已。

棧的特殊之處在於限制了這個線性表的插入和刪除位置,它始終只在棧頂進行。這也就使得:棧底是固定的,最先進棧的只能在棧底。

棧的插入操作,叫做進棧;棧的刪除操作叫做出棧。

1.棧的儲存結構

用來存放棧的資料元素對應的資料儲存結構稱為棧的儲存結構。

1.1棧的順序儲存結構

棧是線性表的特例,所以棧的順序儲存結構其實就是線性表順序儲存結構的簡稱,我們簡稱為順序棧。線性表是用陣列來實現的,對於棧這種只能一頭插入刪除的線性表來說,用陣列下標為0(棧底不變,只需要跟蹤棧頂的變化即可)的一端作為棧底比較合適。

順序棧定義如下:

typedef struct
{

    int data[maxsize];    //定義一個陣列大小為maxsize的陣列,用來存放棧中資料元素
    int top;              //棧頂指標
}SqStack;                 //順序棧定義
複製程式碼

1.2棧的鏈式儲存結構

棧頂放在單連結串列的頭部,用連結串列來儲存棧的的資料結構稱為鏈棧。

鏈棧結點定義如下:

typedef struct LNode
{

    int data;                //資料域
    struct LNode *next;      //指標域
}LNode;                      //鏈棧結點
複製程式碼

2.棧的操作

2.1順序棧的操作

對於順序棧,一共有4個要素,包括兩個特殊狀態和兩個操作。

特殊狀態:
1)棧空狀態:st.top == -1,也有的用st.top = 0表示棧空,這個時候棧頂位置為0。
2)棧滿狀態:st.top == maxsize-1表示棧滿。maxsize為棧中最大元素個數,maxsize-1為棧滿時棧頂元素在陣列中的位置,因陣列位置是從0開始的。

操作:
順序棧的進棧和出棧操作都是在棧頂進行的,所以只需要更改棧頂位置即可達到進棧和出棧的目的。

1)初始化棧:

void initStack(SqStack &st)    //初始化棧
{
    st.top = -1;               //棧頂指標設定為-1
}
複製程式碼

2)進棧操作:

int push(SqStack &st,int x)
{
    if(st.top == maxsize-1)    //判斷棧是否滿,如果滿,則不能進棧
        return 0;
    ++(st.top);                //棧頂指標位置加1
    st.data[st.top] = x        //x進棧,放在st.top位置
        return 1;
}
複製程式碼

3)出棧操作:
出棧與進棧是相對應的操作

int push(SqStack &st,int x)
{
    if(st.top == -1)           //判斷棧是否為空,如果空,則不能進行出棧
        return 0;
    x = st.data[st.top]     //先把棧頂元素取出來
    --(st.top);                 //棧頂指標位置減1
    return 1;
}
複製程式碼

4)簡化版的操作:

/*初始化棧*/
int stack[maxsize];
int top = -1;

/*元素x進棧*/
stack[++top] = x

/*元素x出棧*/
x = stack[top--]

/*注意++top和top++的區別*/
top = 1
a = ++top
b = top++

a = 2
b = 1
複製程式碼

2.2鏈棧的操作

與順序棧對應,鏈棧也有4個元素,包括兩個狀態和兩個操作。

狀態:
1)棧空:lst -> next == NULL,即棧沒有後繼結點時,棧為空。
2)棧滿:如果儲存空間無限大的話,不會存在棧滿的情況。

操作:
鏈棧的進棧就是頭插法建立連結串列的插入操作;出棧就是單連結串列的刪除操作。

鏈棧的插入操作
鏈棧的插入操作

棧的刪除操作
棧的刪除操作

1)鏈棧初始化:

void initStack(LNode *&lst)
{
    lst = (LNode*)malloc(sizeof(LNode));    //製造一個頭結點
    lst -> next = NULL;                     //初始頭結點指向為NULL
}
複製程式碼

2)進棧:

void push(LNode *lst,int x)
{
    LNode *p;
    p = (LNode*)malloc(sizeof(LNode));    //為進棧元素申請結點空間
    p -> next =NULL;                      //初始化結點不指向任何元素

    /*進棧,相當於連結串列的頭插法*/
    p -> data = x;    //將x賦值給p結點的值域
    p -> next = lst -> next;    //p指標指向原lst指向的結點
    lst -> next = p;            //lst指向結點p
}
複製程式碼

3)出棧:

int pop(LNode *lst,int &x)
{
    LNode *p;
    if(lst -> next == NULL)    //棧空則不能出棧,返回0;而棧不會滿,所以在進棧的時候未作判斷
        return 0;
    /*出棧,相當於連結串列的刪除結點*/
    p = lst -> next;
    x = p -> data;
    lst -> next = p -> next;
    free(p);
    return 1;
}
複製程式碼

4)簡化版操作:

/*元素(指標p所指)進棧操作*/
/*類似於頭插法建立連結串列*/
p -> next = lst -> next;    //將空棧的頭結點指向p
lst -> next = p;            //將指標p指向空棧頭結點


/*出棧操作(出棧元素儲存在x中)*/
/*類似於單連結串列的刪除操作*/
p = lst -> next;
x = p -> data;
lst -> next = p -> next;
free(p);
複製程式碼

佇列:

佇列是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表,佇列是一種先進先出的線性表,簡稱FIFO,允許插入的一端稱為隊尾(Rear),允許刪除的一端稱為隊頭(Front)。向隊中插入元素稱為進隊,新元素進隊後成為新的隊尾元素;向隊中刪除元素稱為出隊,元素出隊後,其後繼元素就成為新的隊頭元素。

1.佇列的儲存結構

用來儲存佇列資料元素的資料結構。

1.1佇列的順序儲存結構:

使用順序表儲存佇列時,佇列元素的出隊是在隊頭,即下標為0的地方,當有元素出隊時,出隊元素後面的所有元素都需要向前移動,保證佇列的隊頭始終處在下標為0的位置,此時會大大增加時間複雜度。


用順序表來儲存佇列元素的資料結構稱為佇列的順序儲存結構,定義如下:

typedef struct
{

    int data[maxsize];        //定義陣列
    int front;                //隊首指標
    int rear;                 //隊尾指標
}SqQuene;                     //順序佇列定義
複製程式碼

1.2佇列的鏈式儲存結構:

用連結串列來儲存佇列元素的資料結構稱為佇列的鏈式儲存結構,定義如下:


佇列結點型別定義:

typedef struct QNode
{

    int data;                //資料域
    struct QNode *next;      //指標域
}QNode;                      //隊結點型別定義
複製程式碼

鏈隊型別定義:

typedef struct
{

    QNode *front;        //隊頭指標
    QNode *rear;         //隊尾指標
}LiQuene;                //鏈隊型別定義
複製程式碼

2.佇列操作

2.1迴圈佇列

因為順序佇列出隊時時間複雜度較高,有問題總是要解決的,為什麼一定要讓隊頭出現在下標為0的位置呢?所以有人提出了不去限制佇列元素必須儲存在陣列的前n個單元這一條件,這樣隊頭元素就不需要一定在下標為0的位置。但是隨著佇列元素的出隊,隊頭指標在向後移動,假設隊尾指標已經在maxsize-1的位置,這個時候雖然佇列還有儲存空間,但是隊尾已經無法進隊了,比如下圖這樣:


雖然下標為0和1的位置處還有空間,但是隊尾已經無法再有新元素進隊,我們把這種情況稱為“假溢位”,為了解決這種假溢位的問題,就提出了迴圈佇列的概念,讓佇列的頭尾進行相連,這種頭尾相連的順序儲存結構稱為迴圈佇列。

迴圈佇列需要損失一定的空白,這樣只有在隊空的時候才會出現front=rear。

迴圈佇列的要素:

兩個狀態:
隊空狀態:

qu.rear = qu.front
複製程式碼

隊滿狀態:

(qu.rear+1)%maxsize == qu.front
複製程式碼

兩個操作:
元素x進隊操作(移動隊尾指標)

qu.rear = (qu.rear+1)%maxSize;
qu.data[qu.rear] = x;
複製程式碼

元素x出隊操作(移動隊頭指標)

qu.front = (qu.front+1)%maxSize;
x = qu.data[qu.front];
複製程式碼

初始化佇列演算法:

void initQueue(SqQueue &qu)
{
    qu.front = qu.rear = 0;隊首和隊尾指標重合,並且指向0
}
複製程式碼

進隊演算法:

int enQueue(SqQueue &qu,int x)
{
    if ((qu.rear + 1) % maxSize == qu.front)    //隊滿的判斷條件,如果隊滿則不能進隊,返回0
        return 0;
    qu.rear = (qu.reat+1)%maxSize;              //若隊不滿,先移動隊尾指標
    qu.data[qu.rear] = x;                       //元素x進隊
    return 1;
}
複製程式碼

出隊演算法:

int enQueue(SqQueue &qu,int &x)
{
    if (qu.rear == qu.front)    //隊空的判斷條件,如果隊空則不能出隊,返回0
        return 0;
    qu.front = (qu.front+1)%maxSize;              //若隊不空,先移動隊首指標
    x = qu.data[qu.front] ;                       //元素x出隊
    return 1;
}
複製程式碼

2.2鏈隊:

鏈隊就是採用鏈式儲存結構儲存佇列。鏈隊的四個要素:隊空和隊滿,元素進隊和出隊操作。


隊空狀態:

lqu -> rear == NULLor lqu -> front == NULL
複製程式碼

隊滿狀態:
一般來說不存在隊滿的情況,只要記憶體足夠大。

元素進隊操作(指標p指向進隊元素)

lqu -> rear -> next = p;
lqu -> rear = p;
複製程式碼

元素出隊操作(x儲存出隊元素)

p = lqu -> front;
lqu -> front = p -> next;
x = p -> data;
free(p);
複製程式碼

初始化鏈隊演算法

void initQueue(LiQuene *&lqu)
{
    lqu = (LiQueue*)malloc(sizeof(LiQueue));
    lqu -> front = lqu -> rear = NULL;
}
複製程式碼

判斷隊空演算法

int isQueueEmpty(LiQueue *lqu)
{
    if(lqu -> rear == NULL || lqu -> front == NULL)
        return 1;
    else
        return 0;
}
複製程式碼

入隊演算法

void enQueue(LiQueue *lqu,int x)
{
    QNode *p;
    p = (QNode*)malloc(sizeof(QNode));
    p -> data = x;
    p -> next =NULL;
    if(lqu -> rear == NULL)
        lqu -> front = lqu -> rear = p;    //如果佇列為空,則新結點既是隊尾結點也是隊首結點
    else
    {
        lqu -> rear -> next = p;           //將新結點連結到隊尾,rear指向該結點
        lqu -> rear = p;
    } 
}
複製程式碼

出隊演算法

int deQueue(LiQueue *lqu,int &x)
{
    QNode *p;
    if(lqu -> rear == NULL)    //判斷隊空,如果為空,則不能出隊
        return 0;
    else
        p = lqu -> front;
    if(lqu -> front == lqu -> rear)    //佇列中只有一個結點時的出隊操作
        lqu -> front = lqu -> rear =NULL
    else
        lqu -> front = lqu -> front -> next;
    x = p -> data;
    free(q);
    return 1;
}
複製程式碼

相關文章