棧是一種后進先出的線性表,是最基本的一種數據結構,在許多地方都有應用。
一、什么是棧
棧是限制插入和刪除只能在一個位置上進行的線性表。其中,允許插入和刪除的一端位於表的末端,叫做棧頂(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; }
該程序將數字 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; }
該程序將數字 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語言描述》
