在顺序表和链表中我们讲述了数据结构最基本的两种结构,今天我们就要在这两种数据结构的基础上来继续认识另外一种数据结构——栈。
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
如果你曾经了解过函数栈帧,那么你应该对这种结构不陌生。但是还是要提醒一下,函数栈帧中的栈是一种存储空间,而本篇文章中的栈是一种数据结构,两者在结构上有相似之处。
我们现在已经了解了栈的结构了,那么栈到底怎么实现呢?
前文说到,栈是一种线性表,所以我们可以在顺序表和链表的基础上实现链表。
我们先来实现顺序栈
首先,我们先来写出栈的顺序表结构
typedef int STDataType; typedef struct Stack { STDataType* a; int top;//栈中数据个数 int capacity;//栈总容量 }ST;
首先,我们为了方便储存各种不同类型的数据,我们定义一个SLDataType,如果以后想改变储存数据的类型,直接修改 SLDataType 的定义即可。
接着,我们发现 Stack 中有一个指针,这个指针是用来指向动态内存开辟的空间的,也就是指向数据的。
最后,top 指的是顺序表中元素的个数,capacity 是指顺序表中最多能容纳元素的个数。
接下来,我们完成栈中非常重要的一步——实现接口函数
所有接口函数如下:
// 栈初始化 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);
初始化其实非常好理解,就传一个顺序表的指针,我们将其中的数据先置空。
销毁就是,先将size和capacity置为0,再释放掉储存数据的空间。
void StackInit(ST* ps) { assert(ps); ps->a = NULL; ps->capacity = 0; ps->top = 0; } void StackDestroy(ST* ps) { assert(ps); free(ps->a); ps->a = NULL; ps->capacity = 0; ps->top = 0; }
bool StackEmpty(ST* ps) { assert(ps); return ps->top == 0; } int StackSize(ST* ps) { assert(ps); return ps->top; } STDataType StackTop(ST* ps) { assert(ps); assert(!StackEmpty(ps)); return ps->a[ps->top - 1]; }
由于栈只能从尾部(栈顶)插入/删除元素,所以只用实现尾插/尾删功能(尾插与尾删详细见顺序表)。
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
if (tmp == NULL)
{
printf("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
if (tmp == NULL)
{
printf("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
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;
}
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
上文提到,链栈结构选择头作为栈顶实现入栈(头插)、出栈(头删)比较简单,所以这个结构大家可以动手写一写,与链表实现极为相似,这里不过多赘述。
这次,我们要实现用链尾当栈顶的非双向链表写法。由于这种写法实际用途不大,所以我们只讲解基本实现思路。
typedef int STDataType;
typedef struct StackNode
{
struct StackNode* next;
STDataType data;
}STNode;
typedef struct Stack
{
STNode* top;
STNode* bottom;
}Stack;
这里由于单向链表的节点只能指向下一个节点,所以我们只能在栈结构中记录一下栈顶和栈底的位置。
- 先创建一个新节点,将所给数据存入
- 判断bottom是否为NULL
- 如果是,说明栈中没有数据,则让top与bottom都指向新节点
- 如果不是,则实施尾插,尾插结束后,使top指向尾节点
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
STNode* newnode = (STNode*)malloc(sizeof(STNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = data;
newnode->next = NULL;
if (ps->bottom == NULL)
{
ps->bottom = ps->top = newnode;
}
else
{
ps->top->next = newnode;
ps->top = newnode;
}
}
- 首先,判断是否栈中为空
- 其次,遍历找到尾节点的前一个节点,删除数据,top指向原尾节点前一个元素
- 这里有个特殊情况,如果栈中只有一个元素,那么删除完后,还得把bottom还原为NULL
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
STNode* Del = ps->bottom;
if (ps->bottom == ps->top)
{
free(ps->bottom);
ps->bottom = ps->top = NULL;
return;
}
while (Del->next != ps->top)
{
Del = Del->next;
}
free(ps->top);
Del->next = NULL;
ps->top = Del;
}
剩下的函数都不难实现,大家可以参考下文代码实现。
typedef int STDataType;
typedef struct StackNode
{
struct StackNode* next;
STDataType data;
}STNode;
typedef struct Stack
{
STNode* top;
STNode* bottom;
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈底元素
STDataType StackBottom(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
void StackInit(Stack* ps)
{
assert(ps);
ps->top = NULL;
ps->bottom = NULL;
}
void StackDestroy(Stack* ps)
{
assert(ps);
STNode* cur = ps->bottom;
while (cur)
{
STNode* next = cur->next;
free(cur);
cur = next;
}
ps->bottom = ps->top = NULL;
}
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->bottom == NULL;
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
STNode* newnode = (STNode*)malloc(sizeof(STNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = data;
newnode->next = NULL;
if (ps->bottom == NULL)
{
ps->bottom = ps->top = newnode;
}
else
{
ps->top->next = newnode;
ps->top = newnode;
}
}
void StackPop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
STNode* Del = ps->bottom;
if (ps->bottom == ps->top)
{
free(ps->bottom);
ps->bottom = ps->top = NULL;
return;
}
while (Del->next != ps->top)
{
Del = Del->next;
}
free(ps->top);
Del->next = NULL;
ps->top = Del;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->top->data;
}
STDataType StackBottom(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->bottom->data;
}
int StackSize(Stack* ps)
{
assert(ps);
int sz = 0;
STNode* cur = ps->bottom;
while (cur)
{
sz++;
cur = cur->next;
}
return sz;
}
如果说,栈结构的特点是后进先出,那么有没有一种结构可以做到先进先出呢?
如果有,这个结构该如何实现呢?
了解了栈这种结构以后,下一篇文章,我们就要了解栈的姊妹结构——队列。
这次是白晨第一次使用markdown编辑器编辑,对一些语法尚且不熟悉。如有错误,还请指正,在这里多谢各位的包容。
以上就是本次的分享内容了,喜欢我的分享的话,别忘了点赞加关注哟!
如果你对我的文章有任何看法,欢迎在下方评论留言或者私信我鸭!
我是白晨,我们下次分享见!!!☀️☀️☀️