基本數據結構 -- 棧簡介(C語言實現)


  棧是一種后進先出的線性表,是最基本的一種數據結構,在許多地方都有應用。 

一、什么是棧

  棧是限制插入和刪除只能在一個位置上進行的線性表。其中,允許插入和刪除的一端位於表的末端,叫做棧頂(top),不允許插入和刪除的另一端叫做棧底(bottom)。對棧的基本操作有 PUSH(壓棧)POP (出棧),前者相當於表的插入操作(向棧頂插入一個元素),后者則是刪除操作(刪除一個棧頂元素)。棧是一種后進先出(LIFO)的數據結構,最先被刪除的是最近壓棧的元素。棧就像是一個箱子,往里面放入一個小盒子就相當於壓棧操作,往里面取出一個小盒子就是出棧操作,取盒子的時候,最后放進去的盒子會最先被取出來,最先放進去的盒子會最后被取出來,這即是后入先出。下面是一個棧的示意圖:

 

  

 

二、棧的實現

  由於棧是一個表,因此任何實現表的方法都可以用來實現棧。主要有兩種方式,鏈表實現和數組實現。

2.1 棧的鏈表實現

  可以使用單鏈表來實現棧。通過在表頂端插入一個元素來實現 PUSH,通過刪除表頂端元素來實現 POP。使用鏈表方式實現的棧又叫動態棧。動態棧有鏈表的部分特性,即元素與元素之間在物理存儲上可以不連續,但是功能有些受限制,動態棧只能在棧頂處進行插入和刪除操作,不能在棧尾或棧中間進行插入和刪除操作。

  棧的鏈表實現代碼如下,編譯環境是 win10,vs2015:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "windows.h"

struct stack_node {
    int data;
    struct stack_node *next;
};

typedef struct stack_node *PtrToNode;
typedef PtrToNode Stack;

Stack create_stack();
void push_stack(Stack s, int data);
void pop_stack(Stack s);
int top_stack(Stack s);
int stack_is_empty(Stack s);

int main() 
{
    Stack stack = create_stack();        // 新建一個空棧
    int top_data,i;
    // 壓棧操作,執行10次
    for (i = 0;i < 10;i++) {
        push_stack(stack, i);
    }
    // 出棧操作,執行1次
    pop_stack(stack);
    // 返回棧頂元素的值
    top_data = top_stack(stack);
    printf("%d\n", top_data);

    system("pause");
}

/* 創建一個空棧 */
Stack create_stack()
{
    Stack S;

    S = (Stack)malloc(sizeof(struct stack_node));
    if (S == NULL)
        printf("malloc fair!\n");
    S->next = NULL;

    return S;
}

/* PUSH 操作 */
void push_stack(Stack s,int data) 
{
    // 新建一個結點,用於存放壓入棧內的元素,即新的棧頂
    PtrToNode head_node = (PtrToNode)malloc(sizeof(struct stack_node));
    if (head_node == NULL)
        printf("malloc fair!\n");

    head_node->data = data;            // 添加數據
    head_node->next = s->next;        // 新的棧頂 head_node 的 next 指針指向原來的棧頂 s->next
    s->next = head_node;            // s->next 現在指向新的棧頂
}

/* POP 操作 */
void pop_stack(Stack s) 
{
    PtrToNode head_node = (PtrToNode)malloc(sizeof(struct stack_node));
    if (head_node == NULL)
        printf("malloc fair!\n");

    // 先判斷棧是否為空,若棧為空,則不能再進行出棧操作,報錯
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
    }
    else {
        head_node = s->next;            // head_node 為棧頂
        s->next = head_node->next;        // s->next 指向 head_node->next ,即新的棧頂
        free(head_node);                // 釋放原來棧頂元素所占的內存
    }
}

/* 查看棧頂元素 */
int top_stack(Stack s) 
{
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
        return 0;
    }
    else {
        return s->next->data;
    }
}

/* 判斷棧是否為空 */
int stack_is_empty(Stack s) 
{
    return s->next == NULL;
}
View Code

   該程序將數字 1-9 分別壓棧,然后執行一次出棧操作,最后打印棧頂元素,結果為8。

 

2.2 棧的數組實現

  同樣,棧也可以用數組來實現。使用數組方式實現的棧叫靜態棧

  用數組實現棧很簡單,每個棧都有一個 TopOfStack,用來表示棧頂在數組中的下標,對於空棧,該值為 -1(這就是空棧的初始化)。當需要壓棧時,只需要將 TopOfStack 加 1,然后將數組中該下標處的值置為壓入棧的值即可;出棧操作更簡單,只需要將 TopOfStack 減 1 即可。需要注意的是,對空棧的 POP 操作和對滿棧的 PUSH 操作都會產生數組越界並引起程序崩潰。

  棧的數組實現方法如下,編譯環境是 win10,vs2015:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "windows.h"

#define MinStackSize 5
#define EmptyTOS -1

struct stack_array {
    int capacity;            // 棧的容量
    int top_of_stack;        // 棧頂的下標
    int *array;                // 用於存放棧的數組
};

typedef struct stack_array *ArrayRecord;
typedef ArrayRecord Stack;

Stack create_stack(int stack_capacity);
void make_empty(Stack s);
void push_stack(Stack s, int data);
int top_stack(Stack s);
void pop_stack(Stack s);
int stack_is_empty(Stack s);
int stack_is_full(Stack s);

int main()
{
    Stack stack = create_stack(100);
    int topdata, i;
    for (i = 0;i < 10;i++) {
        push_stack(stack, i);
    }
    pop_stack(stack);
    pop_stack(stack);
    topdata = top_stack(stack);
    printf("%d\n", topdata);

    system("pause");
}

/* 創建一個棧 */
Stack create_stack(int stack_capacity)
{
    Stack S;

    if (stack_capacity < MinStackSize)
        printf("Error! Stack size is too small!\n");

    S = (Stack)malloc(sizeof(struct stack_array));
    if (S == NULL)
        printf("malloc error!\n");

    S->array = (int *)malloc(sizeof(struct stack_array) * stack_capacity);
    if (S->array == NULL)
        printf("malloc error!\n");
    S->capacity = stack_capacity;

    make_empty(S);
    return S;
}

/* 創建一個空棧 */
void make_empty(Stack s)
{
    // 棧頂的下標為 -1 表示棧為空
    s->top_of_stack = EmptyTOS;
}

/* PUSH 操作 */
void push_stack(Stack s, int data)
{
    if (stack_is_full(s)) {
        printf("Error! Stack is full!\n");
    }
    else {
        s->top_of_stack++;
        s->array[s->top_of_stack] = data;
    }
}

/* POP 操作 */
void pop_stack(Stack s)
{
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
    }
    else {
        s->top_of_stack--;
    }
}

/* 返回棧頂元素 */
int top_stack(Stack s)
{
    if (stack_is_empty(s)) {
        printf("Error! Stack is empty!\n");
        return 0;
    }
    else {
        return s->array[s->top_of_stack];
    }
}

/* 檢測棧是否為空棧 */
int stack_is_empty(Stack s)
{    
    // 棧頂的下標為 -1 表示棧為空
    return s->top_of_stack == EmptyTOS;
}


/* 檢測棧是否為滿棧 */
int stack_is_full(Stack s)
{
    // 棧頂的下標為 capacity - 1 表示棧滿了(數組下標從 0 開始)
    return s->top_of_stack == --s->capacity;
}
View Code

  該程序將數字 1-9 分別壓棧,然后執行兩次出棧操作,最后打印棧頂元素,結果為7。

 

2.3 棧的鏈表實現和數組實現的優缺點

  使用鏈表來實現棧,內存動態分配,可以不必擔心內存分配的問題,但是 malloc 和 free 的調用開銷會比較大。

  使用數組實現的棧,需要提前聲明一個數組的大小,如果數組大小不夠,則可能會發生數組越界,如果數組太大,則會浪費一定的空間。一般而言,會給數組聲明一個足夠大而不至於浪費太多空間的大小。除了這個問題,用數組實現的棧執行效率會比用鏈表來實現的高。

  這兩種實現方式中,棧的操作如 PUSH、POP 均是以常數時間運行的,執行速度很快,因此,棧的執行效率通常很高。

 

三、棧的應用

  棧的應用十分廣泛 ,在函數調用、中斷處理、表達式求值、內存分配等操作中都需要用到棧。本文接下來描述一下棧在函數調用中的應用:

  假設有一個函數 f(),現在函數 f() 要調用函數 g() ,而函數 g() 又需要調用函數 h() 。當函數 f() 開始調用函數 g() 時,函數 f() 的所有局部變量需要由系統存儲起來,否則被調用的新函數 g() 將會覆蓋調用函數 f() 的變量;不僅如此,主調函數當前的位置也是需要保存的,以便被調函數執行完后知道回到哪里接着執行調用函數。同樣的,函數 g() 調用函數 h() 時,g() 的相關信息也需要存儲起來。在函數 h() 執行完成后,再從系統中取出函數 g()  的相關信息接着執行函數 g();當函數 g() 執行完成后,從系統中取出函數 f() 的相關信息然后接着執行函數 f()。從這里的描述中可以看到,函數調用時,調用函數的信息是存放在一個后進先出結構中的,顯然,用棧來存放再好不過,用一幅圖演示一下:

 

 

 

參考資料:

《算法導論 第三版》

《數據結構與算法分析--C語言描述》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM