栈
I 栈的概念
I.I 栈
一种特殊的线性表,其只允许在固定的一段进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的元素遵守后进先出(先进后出也可以)LIFO(Last in First Out)的原则。
I.II 压栈
栈的插入操作叫做进栈/压栈/入栈,入数据再栈顶。
I.III 出栈
栈的说着拿出操作叫做出栈。出数据也在栈顶。
II.栈的实现
II.I图解
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。数组的实现我们可以画个图理解理解,如图所示:
链表指针稍微复杂了些。
进栈出栈的示意图为:
II.II代码的实现
下面我们就用数组的形式来实现栈
II.II.I 大体框架
当我们对于链表的学习已经完成后,我们感受到数组的书写相对简单一些,和顺序表的书写一样,我们先创建三个test.c,Stack.c,Stack.h的文件。再头文件中创建结构体,代码如下:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int capacity;
int top;
}ST;
接着书写我们要实现的接口函数:
void StackInit(ST* ps);
void StackPush(ST* ps, STDataType x);
void StackDestroy(ST* ps);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);
int StackSize(ST* ps);// size指向栈顶的下一个
bool StackEmpty(ST* ps); //判断栈是否为空
注意:我们在使用boll这个函数的时候需要引入一个头文件stdbool.h。这样才能正常使用。
II.II.II 栈的初始化
代码如下:
void StackInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
简洁明了,一目了然。先断言,后变空,最后两个指向0.(顺口溜)然后我们就嘎嘎写完了。
在这里ps->top可以有两种写法。一种是上面代码将其变为0,另外一种就是将其变为-1。
II.II.III 入栈
和上上期我们说的顺序表一样,断言以后。我们首先考虑的是空间是否足够满不满的问题。满了,咱就给它扩容。一顿操作之后。我们通过指针指向了新的空间,最后实现入栈的操作,代码如下:
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//考虑空间是否已满的情况
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
II.II.IV 栈的销毁
我们直接将ps->a给free掉,剩下照常,代码如下:
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
II.II.V判断栈是否为空
第一种方法,如果我们ps指向的top为真,那我们就返回真值否则返回假。
if (ps -> top == 0)
{
return true;
}
else
{
return false;
}
事实上我们可以只用一行代码搞定这上面的内容
bool StackEmpty(ST* ps) //判断栈是否为空
{
assert(ps);
/*
* if (ps -> top == 0)
{
return true;
}
else
{
return false;
}
*/
return ps->top == 0;
}
咱直接return 。
II.II.VI出栈
出栈我们首先要用到上面判断栈是否为空,判断后直接top–就完啦。
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
II.II.VII获取栈顶元素
同上判断栈是否为空,判断后我们注意由于我们初始化栈的时候ps->top=0因此我们的top指向的是后面一个元素,获取的时候就要减减来找到。
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
II.II.VIII获取栈中有效元素个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;// size指向栈顶的下一个,
}
额,依然不复杂。写道这里我们所有关于栈的书写就写完了。
III 全部代码
Stack.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int capacity;
int top;
}ST;
void StackInit(ST* ps);// 初始化栈
void StackPush(ST* ps, STDataType x);// 入栈
void StackDestroy(ST* ps);// 销毁栈
void StackPop(ST* ps);// 出栈
STDataType StackTop(ST* ps);// 获取栈顶元素
int StackSize(ST* ps);// 获取栈中有效元素个数
bool StackEmpty(ST* ps); //判断栈是否为空
Stack.c
#include "Stack.h"
void StackInit(ST* ps)
{
assert(ps);
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 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
else
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
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;// size指向栈顶的下一个,
}
bool StackEmpty(ST* ps) //判断栈是否为空
{
assert(ps);
/*
* if (ps -> top == 0)
{
return true;
}
else
{
return false;
}
*/
return ps->top == 0;
}
test.c
#include "Stack.h"
void TestStack1()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPop(&st);
StackPop(&st);
StackPop(&st);
StackPop(&st);
//printf("%d", StackTop(&st));
StackDestroy(&st);
}
void TestStack2()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
printf("%d ", StackTop(&st));
StackPop(&st); // 3 和 4出栈了
printf("%d ", StackTop(&st));
StackPop(&st);
StackPush(&st, 5);
StackPush(&st, 6);
while (!StackEmpty(&st)) // 我们要先取栈顶的数据
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
}
int main()
{
TestStack1();
TestStack2();
return 0;
}
运行结果完美的实现了先进后出,后进先出的原则
在main函数中我们已经先将3和4先出栈了,所以他们先打印出来,然后才是6521。
IV 总结
经过开始的顺序表的书写以及链表的学习应用,我们发现栈的书写还是相对容易的。要想学好数据结构还是要多多练习,多刷OJ题,希望我的文章对你能有所帮助,欢迎各位大佬在评论区纠正我的错误。期待的话,请多多给我点赞吧