🤡博客主页:醉竺
🥰本文专栏:《数据结构与算法》
😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!
✨✨💜💛想要学习更多数据结构与算法点击专栏链接查看💛💜✨✨
目录
一.栈的概念及结构
栈:
- 一种特殊的线性表,其限定仅在表尾进行插入和删除操作的线性表。
- 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
- 或者说最早进入的元素存放的位置叫作栈底(bottom),最后进入的元素存放的位置叫作栈顶 (top)。
- 栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
栈有两种操作,压栈和出栈 。
- 压栈:压栈操作(push)就是把新元素放入栈中,只允许从栈顶一侧放入元素,新元素的位置将会成为 新的栈顶。
- 出栈:出栈操作(pop)就是把元素从栈中弹出,只有栈顶元素才允许出栈,出栈元素的前一个元素将会成为新的栈顶。
其实,我们生活中满足栈这种后进先出的情形非常多,比如往抽屉里放东西,先放进去的肯定 会被堆到最里面,所以只能最后取出,而最后放入的物品往往在最前面或者最上面,所以会被最先取出。
例如,如果用示意图表示用栈存取数据的过程,就会像下图一样:
在上图中,如果分别将数据 a1、a2、a3、a4、a5 存入栈中,那么在将数据出栈的时候,顺序就应该是 a5、a4、a3、a2、a1(与入栈顺序正好相反)。
我们刚刚说过,栈是受限的线性表,比如因为只能在栈顶进行元素的插入和删除操作,所以也无法指定插入和删除操作的位置,所以,栈所支持的操作,可以理解为线性表操作的子集,一般包括栈的创建、入栈(增加数据)、出栈(删除数据)、获取栈顶元素(查找数据)、判断栈是否为空或者是否已满等操作。
二.栈的存储原理
栈既可以用数组来实现,也可以用链表来实现。 数组实现的栈也叫顺序栈;链表实现的栈也叫做链式栈。
栈的数组实现如下:
栈的链表实现如下:
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
三.栈的顺序存储(顺序栈)
所谓顺序栈,就是顺序存储(用一段连续的内存空间依次存储)栈中的数据。
之前我们学习顺序表的时候就提出过2种保存数据的方案同样适合顺序栈。
- 静态存储:通过为一维数组静态分配内存的方式来保存数据。
- 动态存储:通过为一维数组动态分配内存的方式来保存数据。
为了顺序栈中数据存满时可以对栈进行扩容,在这里,我会采用第2种保存数据的方案来编写顺序栈的实现代码。 此外,为了考虑到元素存取的便利性,将数组下标为0的一端作为栈底最合适。
以下栈的存储结构体里面有一个数组a用来当作数组栈存储元素;top为栈顶指针;capacity是数组栈容量(若栈满了可以动态扩张)。
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
// STDataType a[N];
// int top; // 栈顶
//}Stack;
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a; //数组栈
int top; // 栈顶
int capacity; // 容量
}Stack;
四.栈的操作
1.栈操作的函数接口
这里把即将要对栈实现的操作函数接口列出来以便查阅。
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
// STDataType a[N];
// int top; // 栈顶
//}Stack;
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a; //数组栈
int top; // 栈顶
int capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* pst);
// 入栈
void StackPush(Stack* pst, STDataType data);
// 出栈
void StackPop(Stack* pst);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* pst);
// 获取栈顶元素
STDataType StackTop(Stack* pst);
// 获取栈中有效元素个数
int StackSize(Stack* pst);
2.初始化栈
初始化栈的时候,指向栈顶的栈顶指针有两种初始化操作:
- top = 0;
- top = -1;
这两种方式有什么区别呢?又该如何选择呢?
1.进栈操作不一样:
- 当 top = 0 表示栈空时,栈顶指针: 始终指向栈顶元素的位置的下一位。元素先入栈,栈顶指针再加1。
- 当 top = -1 表示栈空时,栈顶指针: 始终指向栈顶元素的位置。栈顶指针先加1,元素再入栈。
2. 出栈操作不一样:
- 当 top = 0 表示栈空时,栈顶指针先减1,元素再出栈。
- 当 top = -1 表示栈空时,栈顶指针先出栈,元素再减1。
在这里我推荐第一种,因为第一种top的数值和栈内元素的数量是一样,top=0也代表栈内元素为0,top=2时,栈内元素也是两个,可以一一对应比较方便。
// 初始化栈
void StackInit(Stack* pst)
{
assert(pst);
pst->a = NULL;
//pst->top = -1; // top指向的是 栈顶元素的位置
pst->top = 0; // top指向的是 栈顶元素的下一个元素的位置
pst->capacity = 0;
}
3.判断栈空
采用第一种top == 0的方式。
// 检测栈是否为空,如果为空返回True,如果不为空返回False
bool StackEmpty(Stack* pst)
{
assert(pst);
/*
if (pst->top == 0)
{
return true;
}
else
{
return false;
}
*/
return pst->top == 0;
}
4.入栈
- 入栈前要判断是否栈满,如果栈已满,则再开辟当前空间的二倍
- 否则将元素放入栈顶,栈顶指针向上移动一个位置(top++)
// 入栈
void StackPush(Stack* pst, STDataType data)
{
assert(pst);
if (pst->top == pst->capacity) //栈为空或者栈满
{
int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("栈内存空间开辟失败");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = data; //元素入栈
pst->top++; //栈顶"游标"向上移动一位
}
5.出栈
- 出栈前要判断是否栈空,如果栈是空的,则出栈失败
- 否则将栈顶元素暂存给一个变量,栈顶指针向下移动一个位置(top--)。
// 出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst)); //断言栈是否为空,为空则报错
pst->top--;
}
6.取栈顶元素
取栈顶元素和出栈不同。取栈顶元素只是把栈顶元素复制一份返回,栈顶指针未移动,栈内元素个数未变。而出栈是指栈顶指针向下移动一个位置,栈内不再包含这个元素。
// 获取栈顶元素
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
return pst->a[pst->top - 1];
}
7.获取栈中有效元素个数
采取第一种top=0为栈空时,这种top的数值和栈内元素的数量是一样,top=0也代表栈内元素为0,top=2时,栈内元素也是两个,可以一一对应比较方便。
// 获取栈中有效元素个数
int StackSize(Stack* pst)
{
assert(pst);
return pst->top;
}
8.销毁栈
// 销毁栈
void StackDestroy(Stack* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
9.栈的应用
- 函数调用:每进入一个函数,就会将临时变量作为一个栈入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
- 浏览器的后退功能:我们使用两个栈,X 和 Y,我们把首次浏览的页面依次压入栈 X,当点击后退按钮时,再依次从栈 X 中出栈,并将出栈的数据依次放入栈 Y。当我们点击前进按钮时,我们依次从栈 Y 中取出数据, 放入栈 X 中。当栈 X 中没有数据时,那就说明没有页面可以继续后退浏览了。当栈 Y 中没有数据,那就说明没有页面可以点击前进按钮浏览了。
三.归纳总结(源代码提供)
创作不易,看到这的希望能点个赞支持我一下吧!这里提供实现栈功能的源代码,需要的直接复制即可运行~
创建三个文件:Stack.h,Stack.c,Test.c
1.Stack.h:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
//typedef int STDataType;
//#define N 10
//typedef struct Stack
//{
// STDataType a[N];
// int top; // 栈顶
//}Stack;
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a; //数组栈
int top; // 栈顶
int capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* pst);
// 入栈
void StackPush(Stack* pst, STDataType data);
// 出栈
void StackPop(Stack* pst);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* pst);
// 获取栈顶元素
STDataType StackTop(Stack* pst);
// 获取栈中有效元素个数
int StackSize(Stack* pst);
// 销毁栈
void StackDestroy(Stack* pst);
2.Stack.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
// 初始化栈
void StackInit(Stack* pst)
{
assert(pst);
pst->a = NULL;
//pst->top = -1; // top指向的是 栈顶元素的位置
pst->top = 0; // top指向的是 栈顶元素的下一个元素的位置
pst->capacity = 0;
}
// 入栈
void StackPush(Stack* pst, STDataType data)
{
assert(pst);
if (pst->top == pst->capacity) //栈为空或者栈满
{
int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("栈内存空间开辟失败");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = data; //元素入栈
pst->top++; //栈顶"游标"向上移动一位
}
// 检测栈是否为空,如果为空返回True,如果不为空返回False
bool StackEmpty(Stack* pst)
{
assert(pst);
/*
if (pst->top == 0)
{
return true;
}
else
{
return false;
}
*/
return pst->top == 0;
}
// 出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst)); //断言栈是否为空,为空则报错
pst->top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));
return pst->a[pst->top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* pst)
{
assert(pst);
return pst->top;
}
// 销毁栈
void StackDestroy(Stack* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
3.Test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void Test()
{
Stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
printf("%d ", StackTop(&st));
StackPop(&st);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
StackDestroy(&st);
}
int main()
{
Test();
return 0;
}