目录
🤠前言
什么是栈?
栈的定义及初始化
栈的定义
栈的初始化
栈的判空
栈顶压栈
栈顶出栈
栈的数据个数
栈的销毁
完整代码
总结
🤠前言
- 学了相当长一段时间的链表,总算是跨过了一个阶段。从今天开始我们将进入栈和队列的学习,相比于链表可以说是有手就行的难度,所以各位老铁可以轻松一波啦,不必太担心。
- 栈和队列我们分开来讲,本篇主要详解栈及其实现
- 栈的特点是先进后出,后进先出(LIFO),这一特点以及进一步运用(单调栈)是一些算法题的突破口,后续我也会分享LeetCode的一些题,希望大家关注点点,以免错过哦!
什么是栈?
我们前面学习的顺序表(数组)以及链表可以说是数据结构的物理结构,而栈和队列则是数据结构的逻辑结构。
这是什么意思呢?
如果把数据结构比作活生生的人,那么物理结构就是人的血肉和骨骼,看得见摸得着,在内存中也实际存储这;逻辑结构则是思想灵魂,看不见摸不着,它依赖于物理结构而存在。
简而言之,栈和队列其实都是由数组或者链表实现的,不过在此基础上加了一些限制条件以适用于不同场景,将其抽化成了一个数据结构
栈的定义及初始化
栈的定义
- 我们采用数组的方式实现栈,因为数组比链表的缓存命中率更高(粗暴点来说cpu访问的速度会更快,访问没用的信息的数量会减少,这里就不具体展开了)。
- 还是定义一个结构体来实现栈,分别为数组(此处应该用malloc,这样不够的时候可以扩容)
- top用来表示栈顶,因为栈只在栈顶一段进行操作
- capacity用来记录栈的容量。
typedef int STDataType;
typedef struct Stack
{
int top;
int capacity;
STDataType* a;
}ST;
栈的初始化
- 在进行插入的时候再开辟空间,为了防止野指针问题,将a置为NULL,capacity显而易见应该也为0
- 关于top就有一点讲究,如果top置为0则指向最后一个数据的下一个,如果置为-1则指向最后一个有效数据。因为每次插入数据后top才++,不是很理解的小伙伴们可以画一个数组模拟一下。
- 我们这里就将top初始化为0,其也间接体现了存储数据的个数。
- 当然传进来的栈不能为空指针啦,不然相当于栈都还没创建,老生常谈的问题了,每个函数接口前都要assert暴力断言一下,下文就不赘述了
以下为函数接口实现:
void StackInit(ST* ps)
{
assert(ps);
ps->capacity = ps->top = 0;//指向最后一个数据的下一个
ps->a = NULL;
}
栈的判空
上文提到top间接表示了存储数据的个数,所以我们直接判断top是否为0即可。
以下为函数接口实现:
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
栈顶压栈
- 在数据结构中的学习当中,插入数据往往需要考虑扩容的问题,此处也是。当top==capacityd的时候我们就扩容
- 插入后top需要++一下,而扩容后capacity则需要更新(易忘)
- 这里应该包括了当top==0的情况,因为初始化的时候没有开辟空间
以下为函数接口实现:
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//当内存满了则需要扩容
if (ps->top== ps->capacity)
{
int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
STDataType *tmp=(STDataType*)realloc(ps->a, newcapacity);
if (tmp == NULL)
{
perror("malloc fail\n");
return;
}
else
{
ps->capacity = newcapacity;
ps->a = tmp;
}
}
//没满就直接压栈即可
ps->a[ps->top] = x;
ps->top++;
}
栈顶出栈
- 删除数据,往往需要考虑数据结构是否为空的情况,这里就可以用到上文的StackEmpty函数接口了,增加了代码的可读性
- 和顺序表删除数据一样,只需要top--即可,并不需要真的抹除其数据,下次入栈的时候新的数据就会覆盖原来的数据。
- capacity不需要变化,因为其表示栈的容量
以下为函数接口实现:
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
栈的数据个数
- 上文说了top初始化为0的时候代表数据个数,所以这里直接返回top即可
- 数据个数一定为整数,所以用int即可,unsigned int也行
以下为函数接口实现:
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
获取栈顶元素
- 函数返回类型应该为数组存储的数据类型,而不要思维固化的写为int
- 因为top初始化为0,指向最后一个数据的下一个位置,所以我们要返回top-1位置的数据
- 当栈为空的时候肯定获取不了啦,所以这里还是assert断言爆打
以下为函数接口实现:
STDataType StackTop(ST* ps)//获得栈顶元素
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
栈的销毁
切记不可直接free掉代表栈的结构体就以为把栈销毁了,要先free掉结构体里的数组,然后再free掉代表栈的结构体,不然会内存泄漏!!!
以下为函数接口实现:
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
free(ps);
}
完整代码
void StackInit(ST* ps)
{
assert(ps);
ps->capacity = ps->top = 0;//指向最后一个数据的下一个
ps->a = NULL;
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//当内存满了则需要扩容
if (ps->top== ps->capacity)
{
int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
STDataType *tmp=(STDataType*)realloc(ps->a, newcapacity);
if (tmp == NULL)
{
perror("malloc fail\n");
return;
}
else
{
ps->capacity = newcapacity;
ps->a = tmp;
}
}
//没满就直接压栈即可
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
STDataType StackTop(ST* ps)//获得栈顶元素
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
free(ps);
}
总结
😊相比于链表,栈实现起来简直不要简单太多,但是大家别懈怠,后面还有二叉树在等着我们呢,现在只是过渡期,继续努力哦!
❤️我也会继续输出数据结构相关的博客的,希望大家多多支持!!!
感谢阅读本小白的博客,如果错误请指出,一定虚心采纳!