概念
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素的操作,进行数据的插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则
压栈:栈的插入操作叫做进栈/压栈/入栈,入的数据在栈顶
出栈:栈的删除操作叫做出栈,出的数据也在栈顶
栈的结构就像平常所见的羽毛球桶一样,从顶端放入羽毛球,从顶端取出羽毛球
如下图,入栈的顺序是1,2,3,那么出栈顺序就是3,2,1
栈可以使用链表实现,为了方便理解,降低难度,这篇文章中我们使用数组实现
如果想尝试链表实现但还不会链表,可以看我之前写的这篇文章:数据结构——链表
接下来,我们用代码来实现栈的结构以及它的相关操作
代码实现
栈结构
栈的内部,有一个数组用于存放数据,因为栈是可以直接获取顶端元素的,因此还要有一个记录元素个数的整型,为了实现数组的扩容,还需要一个整型用于记录当前数组的容量
typedef int STDataType;//方便修改储存的数据类型
typedef struct Stack
{
STDataType* a;//保存数据
int top;//记录数据个数
int capacity;//记录数组容量
}ST;
初始化
对栈的结构进行初始化
函数的接口处传入栈的指针
1.对栈的指针断言,如果指针为空则结束程序
2.将数组置空
3.将元素个数和容量初始化为0
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶元素的下一个数据
pst->top = 0;
pst->capacity = 0;
}
相信你已经看到上面代码注释中,top指向栈顶元素的下一个数据,这是为什么呢?
我们在前面讲到,栈使用数组保存数据,top记录数据个数,而数组元素下标是从0开始的。如果top指向栈顶元素,那么当top=0时,top指向数组首元素(即此时有栈顶数据),但是数据个数为0,两者相悖。
因此在这里规定top指向栈顶的下一个位置,那么栈顶应该为top-1
销毁
对栈的内存占用进行销毁
函数的接口传入栈的指针
1.断言栈指针
2.将数组置空
3.将元素个数和容量置0
void STDestroy(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
入栈
从栈顶压入数据
扩容
用newcapacity保存新的容量,如果容量为0那么新容量赋值为4,否则容量扩大为原来的2倍
为了平衡内存和使用性能,一般扩容的倍数是1.5或2倍
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity *sizeof(STDataType));
入栈函数
函数接口传入栈指针和新数据x
1.断言栈指针
2.判断元素个数是否和容量相等,如果相等进行扩容
3.判断tmp是否为空指针,如果为空说明空间开辟失败
4.将新容量和空间赋值给capacity和a
5.将x赋值给栈顶的下一个位置
6.元素个数top加1
void STPush(ST* pst, STDataType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
出栈
删除栈顶元素
函数接口传入栈指针
1.断言栈指针
2.断言元素个数,如果个数为0则不能出栈
3.元素个数top减1,此时无法访问栈顶数据,相当于栈顶被删除
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
取栈顶
获取栈顶数据
函数接口传入栈指针,返回栈顶数据
1.断言栈指针和元素个数
2.将栈顶元素返回
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
判空
判断栈是否为空
函数接口传入栈指针,返回bool类型值
1.断言栈指针
2.直接返回比较结果
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
获取数据个数
函数接口传入栈指针,返回整型
1.断言栈指针
2.直接返回栈的数据个数
int STSize(ST* pst)
{
assert(pst);
return pst->capacity;
}
注意
在对结构体或类进行操作时,即使像获取个数、判空这种一行可以实现的功能,也不要直接访问,而是通过函数实现
例如下面的代码就是一种危险操作:
ST stack;
if(stack.capacity == 0)//直接对结构体进行访问
{
printf("栈为空\n");
}
没有函数内部加以限制,直接访问可能会错误修改数据或者访问的并不是想要的数据
因此为了提高安全性,一定要通过封装好的方法函数访问内部数据