標籤:i++ 動態 動態棧 free pen 時間 容量 技術分享 因此
棧是一種後進先出的線性表,是最基本的一種資料結構,在許多地方都有應用。
一、什麼是棧
棧是限制插入和刪除只能在一個位置上進行的線性表。其中,允許插入和刪除的一端位於表的末端,叫做棧頂(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 -1struct 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語言描述》
基本資料結構 -- 棧詳解