1.前言
我们写代码的时候,肯定都曾经出现过栈溢出的报错提示。
Stack around the viarble ‘xxx’ was corrupted
本篇博客将介绍栈的结构及其模拟实现,但是仅从数据结构的层面上介绍和实现栈,不会设计深层的计算机储存原理。
2.栈的概念和结构
堆栈又名栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
简单来说,栈是一种“先进后出”的数据结构。栈就像是一个竖着放置的木箱,如果你想要取出木箱底层的物品,就一定需要先取出压在底层物品上的其他东西。
图示:
3.栈的模拟实现
首先声明,栈的模拟实现是在顺序表的基础上完成的,只不过我们禁止了这个顺序表的头插、头删、中间插、中间删等操作。但是当然,你也可以用链表来模拟实现链表,这完全是可以做到的,但是今天我们只展示用顺序表来模拟实现栈。如果你想更深入了解顺序表–>数据结构|顺序表的类型和实现<–
3.1.头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空
bool StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
这里我们实现的栈是一个动态扩容的栈,我们先创建一个叫做Stack
的结构体,并typedef
它,这样在后续使用这个结构体时就不用打struct
了。同样,在上面我们把int
类型typedef
成SLDataType,这样如果我们要存储其他类型的数据时,只需要把这里的int的改成需要存储的类型,就可以一键更改存储的数据类型了。
在这个结构体中,a
是指向栈开辟的空间的指针,top
是用来记录栈内数据个数的变量,同时top-1
也代表栈顶元素的下标,而capacity
是用来记录当前栈的最大容量的。
如果你想模拟静态栈,可以把上面换成:
#define MAX 100
typedef int STDataType;
typedef struct Stack
{
StackCapacity[MAX];
int top; // 栈顶
3.2.栈的初始化
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = ps->top= 0;
}
在初始化栈时,需要断言传过来的结构体指针变量是否为空,然后再将结构体内的存储栈中元素的数组的指针a
制空,把容量和栈顶个数初始化为0,这样栈的初始化就完成了。
3.3.入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = data;
ps->top++;
}
在上面初始化capacity
时,我们将其赋值为0,所以在首次添加数据的时候要把NewCapacity
赋值为4,后续数据满后再每次扩容二倍,这里用了一个三目表达式实现这个功能。
在插入之前,我们要确保栈内的元素个数所占的空间大小没有超过开辟的空间大小,如果空间不够,就需要通过realloc
函数增大开辟的空间。注意,这里为了防止开辟新空间失败导致销毁了旧空间,我设置了一个临时变量tmp
来暂时储存开辟的新空间,再判断tmp
不为空后,再把指针a
赋给tmp
,也就是指向新开辟的空间。
接着我们只需要把插入进来的数据放到栈里,再把用来记录数据个数的top++
,这样入栈的操作就完成了。
图示:
3.4.出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
由于栈是一种**“先入后出”的数据结构,在一次出栈操作时,只有栈顶元素会被弹出。在代码层面,我们不需要真的删除栈顶的数据,只需要让栈顶的数据变为无效数据**就可以了,也就是把栈的数据个数top--
,把栈的倒数第二个元素变成栈顶,这样如果后续再进行入栈操作,原栈顶的数据就会被覆盖,相当于实现了出栈的操作。
图示:
3.5.获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
由于top记录的是栈的有效数据个数,所以栈顶数据的元素下标是top-1
,这就和访问数组一样。
3.6.获取栈内元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
top
记录的是有效数据个数,所以只要返回top
就可以获取到栈内的元素个数了。
3.7.检测栈是否为空
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
这里检测栈是否为空用的是bool
来实现,这样写的好处是代码十分简洁。你也可以用int
类型作为返回值,这样的效果是完全一样的。
3.8.销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
当我们使用完栈且不再需要时,需要释放整个栈开辟的空间。当然如果你的程序已经走向结束,其实不主动销毁栈,内存也会自行回收这块空间。但如果我们的栈是在24小时不间断运行的服务器上使用,销毁链表的操作就尤为重要了。
4.完整代码在这里
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空
bool StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = ps->top= 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = data;
ps->top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}