1. 栈的概念及结构
栈: 一种特殊的线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。 栈中的数据元素遵守 后进先出 LIFO(Last In First Out) 的原则。
压栈: 栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈: 栈的删除操作叫做出栈。出数据也在栈顶。
栈一般使用 数组或者链表 实现,本文使用数组实现栈的结构。
相对而言,数组的结构实现更优一些,因为数组在尾上插入、删除数据的代价比较小。
使用链表实现栈。如果用尾做栈顶,尾插尾删,要设计成双向链表,否则删除数据效率低。如果用头做栈顶,头插头删,可以设计成单链表。
数组实现栈:
链表实现栈:
2. 栈的实现
代码的实现将放在三个文件中,分别是 Stack.h(用于声明)、Stack.c(用于定义)、Test.c(用于测试)。
Stack.h 文件
#pragma once #include<stdio.h> #include<stdlib.h> #include <string.h> #include <assert.h> #include <stdbool.h> typedef int STDataType; // 栈的结构 typedef struct Stack{ STDataType* a; // 存储数据 int top; // 栈顶的位置 int capacity; // 栈的容量 }ST; // 栈的初始化 void StackInit(ST* ps); // 销毁栈 void StackDestroy(ST* ps); // 入栈 void StackPush(ST* ps, STDataType x); // 出栈 void StackPop(ST* ps); // 获取栈顶元素 STDataType StackTop(ST* ps); // 获取栈中元素数目 int StackSize(ST *ps); // 判断栈是否为空 bool StackEmpty(ST* ps);
2.1 栈的初始化
Stack.c 文件
#include "Stack.h" // 栈的初始化 void StackInit(ST* ps){ assert(ps); ps->a = NULL; ps->top = 0; ps->capacity = 0; }
2.2 销毁栈
思路: 将开辟的数组空间 free 掉,把其余数据置为 0。
Stack.c 文件
#include "Stack.h" // 销毁栈 void StackDestroy(ST* ps){ assert(ps); free(ps->a); ps->a = NULL; ps->capacity = ps->top = 0; }
2.3 入栈(在栈顶插入数据)
思路: 在插入数据前,需要判断栈的空间是否足够,如果 top == capacity,则栈的空间已满,需要扩容。如果未满,则压入数据,top++。
Stack.c 文件
#include "Stack.h" // 入栈 void StackPush(ST* ps, STDataType x){ assert(ps); // 插入数据时如果空间不够,就扩容 if (ps->top == ps->capacity){ // 刚创建的栈没有空间,可以采用三目操作符,先分配 4 个空间的大小 int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2; STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType)); if (tmp == NULL){ printf("realloc fail\n"); exit(-1); } ps->a = tmp; // 扩容 ps->capacity = newcapacity; } // 数据压栈(放到栈顶) ps->a[ps->top] = x; // 记录数据的个数,同时也能知道栈顶的数据值是多少 ps->top++; }
Test.c 文件
#include "Stack.h" void TestStack1(){ ST st; StackInit(&st); StackPush(&st, 1); StackPush(&st, 2); StackPush(&st, 3); StackPush(&st, 4); } int main(){ TestStack1(); return 0; }
2.4 出栈(栈顶数据的删除)
思路: 出栈至少要保证栈中有元素,也就是 ps->top > 0。如果符合条件,则将栈顶位置下移一位。
Stack.c 文件
#include "Stack.h" // 出栈 void StackPop(ST* ps){ assert(ps); // 如果要出栈,至少要保证栈中有元素 assert(ps->top > 0); ps->top--; }
2.5 获取栈顶元素
思路: 压栈时,先将数据放入后 top++。栈顶的元素下标为 top-1,所以访问栈顶元素就是访问数组所对应下标为 top-1 的元素。
Stack.c 文件
#include "Stack.h" // 获取栈顶元素 STDataType StackTop(ST* ps){ assert(ps); assert(ps->top > 0); // top 是数据的个数,减去 1 就是对应栈顶的数据的下标 return ps->a[ps->top - 1]; }
2.6 获取栈中元素数目
思路: 数组下标从 0开始,获取栈中有效元素个数,即 top 的值,所以直接返回 top 即可。
Stack.c 文件
#include "Stack.h" // 获取栈中元素数目 int StackSize(ST *ps){ assert(ps); return ps->top; }
2.7 判断栈是否为空
思路: 判断 top 的值是否为 0 即可,top 为 0 说明栈为空。
Stack.c 文件
#include "Stack.h" // 判断栈是否为空 bool StackEmpty(ST* ps){ assert(ps); // 直接判断 top 的值是否为 0 return ps->top == 0; }
2.8 遍历栈的数据
思路: 遍历输出栈是要付出代价的,因为栈中的元素遵循后进先出的原则。当栈不为空时, 遍历输出栈。要访问下一个元素需要把原先的栈顶元素删除,然后再访问栈顶,直到栈中的元素为空。
Test.c 文件
#include "Stack.h" // 遍历栈的数据 void TestStack2(){ ST st; // 栈的初始化 StackInit(&st); // 入栈 StackPush(&st, 1); StackPush(&st, 2); StackPush(&st, 3); StackPush(&st, 4); StackPush(&st, 5); // 遍历栈的数据 while (!StackEmpty(&st)){ printf("%d ", StackTop(&st)); StackPop(&st); } // 销毁栈 StackDestroy(&st); } int main(){ TestStack2(); return 0; }
运行结果: