前言
本章節開始資料結構第二篇,棧和佇列:
棧:
- 棧的儲存結構
- 棧的基本操作
佇列:
- 佇列的儲存結構
- 佇列的基本操作
棧
我們把類似於彈夾那種先進後出的資料結構稱為棧,棧是限定僅在表尾進行插入和刪除操作的線性表,我們把允許插入和刪除的一端稱為棧頂,另一端稱為棧底,不含任何資料元素的棧稱為空棧,棧又稱後進後出的線性表,簡稱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 == NULL; or 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;
}
複製程式碼