1. 程式人生 > >順序棧/鏈式棧

順序棧/鏈式棧

        棧是是一種限定性的線性表,它將線性表的插入和刪除限定為僅在表的一端進行。將表中允許插入和刪除的一端成為棧頂。所以棧頂的位置是不斷動態變化的。它具有“後進先出”的特點。因為棧是由線性表實現的,所以,棧有兩種儲存結構:順序儲存和鏈式儲存。對應的棧成為順序棧和鏈式棧。下面,分別來介紹這兩種棧的相關操作。

一,順序棧

它與順序表類似,即用一組地址連續的空間存放棧中的元素。之前的順序表是通過陣列來實現,其中陣列的長度必須圖以前設定為一常數。當元素個數超過陣列最大長度時,就會插入失敗。

        下面,來實現可以擴容的順序棧。即當元素個數超過設定的最大長度時,可以在申請更大的記憶體來存放元素。

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;
}