順序棧/鏈式棧
棧是是一種限定性的線性表,它將線性表的插入和刪除限定為僅在表的一端進行。將表中允許插入和刪除的一端成為棧頂。所以棧頂的位置是不斷動態變化的。它具有“後進先出”的特點。因為棧是由線性表實現的,所以,棧有兩種儲存結構:順序儲存和鏈式儲存。對應的棧成為順序棧和鏈式棧。下面,分別來介紹這兩種棧的相關操作。
一,順序棧
它與順序表類似,即用一組地址連續的空間存放棧中的元素。之前的順序表是通過陣列來實現,其中陣列的長度必須圖以前設定為一常數。當元素個數超過陣列最大長度時,就會插入失敗。
下面,來實現可以擴容的順序棧。即當元素個數超過設定的最大長度時,可以在申請更大的記憶體來存放元素。
1. 順序棧的結構
首先,因為該順序棧要實現擴容,則不能用陣列來實現。因此可以動態申請一塊記憶體,將棧中元素放入其中。通過動態記憶體申請返回的指標來以陣列的形式訪問棧中元素。
其次,該動態記憶體的大小初始時可以設定為已預設值,如果超過預設值時,可以重新申請更大的記憶體,從而達到擴容的目的。
最後,需要知道棧中實際元素的個數。來記錄順序表中最後一個元素所在的位置下標。以及與預設長度進行對比。
所以,順序棧的結構定義如下:
typedef char SeqStackType; //定義順序棧的結構 typedef struct SeqStack { SeqStackType* data;//動態申請順序棧的記憶體空間 int size;//順序棧的實際長度 int capacity;//順序棧的最大長度 }SeqStack;
2. 初始化順序棧
順序棧要存放元素,首先要有一塊記憶體空間,在申請時要指定記憶體大小,初始時,先預設申請1000個char型的空間大小。該空間由動態申請而得,由棧中的成員data指向。從而對棧中元素進行訪問。因為,初始時,棧中沒有節點,所以,實際長度size為0。
程式碼如下:
//初始化順序棧,stack為順序棧的的指標 void InitSeqStack(SeqStack* stack) { if(stack == NULL) { //非法輸入 return; } stack->size = 0;//初始時實際長度為0 stack->capacity = 1000;//初始使順序棧的長度設定為1000 stack->data = (SeqStackType*)malloc(sizeof(SeqStackType)*(stack->capacity));//為順序棧申請動態記憶體 }
因為棧具有“後進先出”的特點,在出,入棧時,必須在棧的一端進行。如果在順序表的頭部進行入棧,則需要將棧中原有元素依次後移,此時需要遍歷整個順序棧。同理,出棧時,也需要遍歷。
所以,採取在順序棧的尾部進行出,入棧。因為size-1可以用來表示順序棧的最後一個元素所在的下標,所以在入棧時可以直接將下標為size的位置設定為指定元素。出棧時,只需將下標為size-1處的值刪除即可。而無需遍歷整個順序棧。
3. 尾插入棧
(1)首先需要判定順序棧的長度是否等於預設長度。如果相等,此時需要擴容後,方可入棧。如果不等,直接入棧。
(2)在擴容時,因為原記憶體空間有限,所以需要動態申請更大的空間,新空間的大小可以自定義。申請成功後,將原空間中內容拷貝到新空間中,然後原空間,最後使順序棧中的data指向新空間即可。
(3)在入棧時,只需將下標為size的位置處的值設定為指定值,然後順序棧長度加1即可。
程式碼如下:
//尾插入棧
void SeqStackPush(SeqStack* stack,SeqStackType value)
{
if(stack == NULL)
{
//非法輸入
return;
}
if(stack->size >= stack->capacity)
{
//初始的順序棧已滿,此時需要擴容
stack->capacity = stack->capacity*2 + 1;
//申請擴容後的動態記憶體
SeqStackType* new_data = (SeqStackType*)malloc(sizeof(SeqStackType)*(stack->capacity));
//將原記憶體中的內容拷貝到新記憶體中
int i = 0;
for(;i < stack->size;i++)
{
new_data[i] = stack->data[i];
}
//釋放原順序棧中的記憶體
free(stack->data);
//將擴容後的記憶體儲存在順序棧的結構中
stack->data = new_data;
}
//尾插入棧
stack->data[stack->size++] = value;
return;
}
4. 尾刪出棧
(1)首先判定順序棧中是否為空,為空則出棧失敗
(2)若不為空,直接將順序棧長度減1即可。此時,原尾元素已變為無效元素。
程式碼如下:
//尾刪出棧
void SeqStackPop(SeqStack* stack)
{
if(stack == NULL)
{
//非法輸入
return;
}
if(stack->size == 0)
{
//空順序棧
return;
}
//將尾元素設定為無效元素即可
--stack->size;
}
5. 取棧頂元素
(1)判斷順序棧是否為空,為空則失敗
(2)不為空,因為順序棧的尾部為棧頂所在位置,所以只需將下標為size-1處的元素儲存下來即可。
程式碼如下:
//取棧頂元素,返回值:-1代表出錯返回,0代表成功返回
int SeqStackTop(SeqStack* stack,SeqStackType* value)
{
if(stack == NULL || value == NULL)
{
//非法輸入
return -1;
}
if(stack->size == 0)
{
//空順序棧
return -1;
}
*value = stack->data[stack->size - 1];
return 0;
}
6. 銷燬順序棧
順序棧在初始化前並沒有申請的記憶體空間和元素,所以銷燬後要將順序棧恢復為初始化前的狀態。即釋放data指向的動態申請的記憶體,將有效長度和實際長度均置為0即可。
程式碼如下:
//銷燬順序棧
void Destory(SeqStack* stack)
{
stack->size = 0;
stack->capacity = 0;
free(stack->data);
return;
}
二,鏈式棧
鏈式棧是通過單鏈表來實現的。每次入棧一個元素,向連結串列中新增一個節點,出棧一個元素,釋放一個節點。因為棧具有“後進先出”的特點,如果每次在連結串列的尾部進行插入和刪除,就要遍歷整個連結串列來找到尾節點。而在頭部進行插入和刪除時,只需根據頭指標即可找到連結串列的首元素結點。而無需遍歷連結串列。所以鏈式棧的出,入棧通過對連結串列進行頭刪和頭插來實現。
1. 鏈式棧的結點結構
鏈式棧是有單鏈表來實現的,所以與單鏈表的結點結構相同。由資料域和指向下一個結點的next域組成。
typedef char LinkStackType;
//定義鏈棧的節點結構
typedef struct LinkStackNode
{
LinkStackType data;
struct LinkStackNode* next;
}LinkStackNode;
2. 鏈式棧的初始化
與單鏈表的初始化相同,可以通過檢視部落格“單鏈表的基本操作”來詳細瞭解。
//初始化鏈棧
void LinkStackInit(LinkStackNode** pstack)
{
if(pstack == NULL)
{
//非法輸入
return;
}
*pstack = NULL;
}
3. 鏈式棧的入棧操作
鏈式棧的入棧是由單鏈表的頭插來實現的。這裡也不詳細說明。
//建立節點
LinkStackNode* CreateNode(LinkStackType value)
{
LinkStackNode* new_node = (LinkStackNode*)malloc(sizeof(LinkStackNode));
new_node->data = value;
new_node->next = NULL;
return new_node;
}
//頭插入棧
void LinkStackPush(LinkStackNode** pstack,LinkStackType value)
{
if(pstack == NULL)
{
//非法輸入
return;
}
//建立節點
LinkStackNode* new_node = CreateNode(value);
//將新節點的next指向原來的首原節點來做為新的首原節點
new_node->next = *pstack;
//使頭指標指向新的首原節點
*pstack = new_node;
return;
}
4. 鏈式棧的出棧操作
鏈式棧的出棧操作是通過單鏈表的頭刪來實現的。
(1)首先,判斷連結串列是否為空,為空則出棧失敗
(2)不為空,頭指標指向第二個節點,使第二個節點作為新的首原節點
(3)釋放原來的首原節點
程式碼如下:
//銷燬節點
void DestoryNode(LinkStackNode* node)
{
free(node);
}
//頭刪出棧
void LinkStackPop(LinkStackNode** pstack)
{
if(pstack == NULL)
{
//非法輸入
return;
}
if(*pstack == NULL)
{
//空鏈棧
return;
}
//儲存要刪除的首原節點
LinkStackNode* to_delete = *pstack;
//使頭指標指向第二個節點
*pstack = to_delete->next;
//釋放要刪除的節點
DestoryNode(to_delete);
return;
}
5. 取棧頂元素
此時的棧頂位於連結串列的頭部。
(1)如果連結串列為空,則失敗
(2)若不為空,將首原節點的資料域儲存下來即可。
程式碼如下:
//取棧頂元素
int LinkStackTop(LinkStackNode* stack,LinkStackType* value)
{
if(stack == NULL)
{
//空連結串列
return -1;
}
*value = stack->data;
return 0;
}
6. 鏈式棧的銷燬
初始化前鏈式棧只有一個頭指標,所以銷燬後要恢復到初始化前的狀態。所以
(1)遍歷連結串列將各個節點進行釋放
(2)為避免頭指標變成野指標,將頭指標置空
程式碼如下:
//銷燬鏈式棧
void LinkStackDetory(LinkStackNode** stack)
{
if(stack == NULL)
{
//非法輸入
return;
}
//遍歷連結串列各節點對其進行釋放
LinkStackNode* to_delete = *stack;
while(to_delete != NULL)
{
LinkStackNode* next_node = to_delete->next;
free(to_delete);
to_delete = next_node;
}
//將頭指標置空
*stack = NULL;
return;
}