目录
前言
前面我们学习并实现了顺序表与链表的接口,顺序表与链表都是线性表的一种,即在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的;这期我们继续来介绍一种特殊的线性表------栈;
一、栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶 ,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
- 压栈 :栈的插入操作叫做进栈/压栈/入栈 ,入数据在栈顶。
- 出栈 :栈的删除操作叫做出栈 。出数据也在栈顶。
二、栈的实现方式
栈的实现一般可以使用数组 或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小;所以这里我们用数组的方式来实现;
- 用数组的方式 实现:
- 数组的起始作为栈底,栈底在数组的右边;
- 数组的起始作为栈底,栈底在数组的右边;
- 用链表的方式 来实现
- 如果是双向链表:链表的头和尾都可以作为栈顶:
- 如果用单链表:由于在出栈的时候我们要寻找下一个栈顶比较麻烦,所以我们只能将栈顶设置在头结点:
这里我们用数组的方式实现:
三、栈的实现
3.1 基本结构
我们用数组的方式实现栈还分为以下两种:
- 定长的静态栈
- 可以动态增长的动态栈
在实际项目应用中,当我们知道这个项目一定不会超过一个固定的内存大小(如1000个整形空间),我们就可以使用这种结构:
c
#define N 1000
//定长的静态栈
typedef struct Stack
{
STDataType a[N];
int _top; //栈顶
}Stack;
反之,我们用动态增长的栈:
c
// 支持动态增长的栈
typedef struct Stack
{
STDataType* _a;
int _top; //栈顶指针
int _capacity; //动态容量
}Stack;
这里我们实现用动态增长的栈:
3.2 栈的基本功能接口
栈的初始化
这个接口没什么好说的,不过我们要注意一下capacity的初始值,以便后面入栈的时候方便扩容:
c
//栈的初始化
void StackInit(Stack* pst)
{
assert(pst);
//这样会给后面扩容造成麻烦
/*pst->_a = NULL;
pst->_top = 0;
pst->_capacity = 0;*/
pst->_a = (STDataType*)malloc(sizeof(STDataType) * 4);
pst->_top = 0;
pst->_capacity = 4;
}
栈的销毁
c
//栈的销毁
void StackDestory(Stack* pst)
{
assert(pst);
free(pst->_a); //释放开辟的空间
pst->_a = NULL;
pst->_top = pst->_capacity = 0;
}
3.3 入栈接口
根据前面顺序表的插入操作我们也可以得知,在入栈之前我们需要进行扩容判断,再进行入栈操作:
c
void StackPush(Stack* pst, STDataType x)
{
//检查是否需要扩容
if (pst->_top == pst->_capacity)
{
pst->_capacity *= 2;
STDataType* tmp = (STDataType*)realloc(pst->_a, sizeof(STDataType) * pst->_capacity);
if (tmp == NULL)
{
perror("relloc");
exit(-1);
}
else
{
pst->_a = tmp;
}
}
pst->_a[pst->_top] = x;
pst->_top++;
}
这时候有聪明的同学有个问题了:为什么不将这个扩容代码写出一个函数接口呢?其实你实现完整个栈就会发现,只有这会用到空间的扩容的;所以我们不再将其单独开辟一个接口;
3.4 出栈接口
c
void StackPop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0); //判断一下是否为空栈
//只需要将top一走,可以不用初始化出栈的值
//pst->_a[pst->_top] = 0;
pst->_top--;
}
3.5 栈的其它接口
获取数据的个数接口
我们要清楚top的值就是数据的个数
c
//获取数据的个数
int StackSize(Stack* pst) //考虑到内存和统一性问题我们这里也传一级指针
{
assert(pst);
return pst->_top;
}
栈判断是否为空接口
c
//判空,1是空,0是非空
int StackEmpty(Stack* pst)
{
assert(pst);
//return pst->_top == 0 ? 1 : 0;
return !pst->_top; //巧写,更加简便
}
获取栈顶数据接口
c
//获取栈顶数据
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(pst->_top);
return pst->_a[pst->_top-1];
}
注:为什么要实现这些简单的接口,直接调用结构体不好吗?
我们要知道每个人实现相同的结构并不是完全相同的,就像这个栈的实现:
我们在初始化的时候默认pst->_top=0,这就导致我们访问栈顶数据是pst->_a[pst->_top-1];
如果有人在初始化的时候默认pst->_top=-1(也是允许的),那我们访问栈顶数据是pst->_a[pst->_top];
在实际开发中就找成许多麻烦了;
3.6 测试
注:细心的同学会发现我们并没有在前面写栈的打印接口,这是因为栈必须满足先进后出的规律,我们在读取栈顶元素候必须要经过出栈才能继续向下读取;
所以我们一般这样打印出栈里的元素:
c
while (!StackEmpty(st))
{
printf("%d ", StackTop(st));
StackPop(st); //出栈
}
printf("\n");
我们写出测试函数:
c
void Test1() {
Stack st;
StackInit(&st); //初始化
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
//遍历
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st); //出栈
}
printf("\n");
printf("%d ", StackSize(&st)); //打印大小,已经全部出栈,size为0;
StackDestory(&st); //销毁
}
运行Test1函数结果:
符合预想结果,测试通过。
总结
这期我们用C语言实现了栈的几个基本接口,我们有了前面的基础,我们发现这并不是很难;下期见!
26考研加油!