小白都能看懂的 “栈”

什么是栈?首先引用维基百科的解释:

栈(stack)是计算机科学中的一种抽象资料类型,只允许在有序的线性资料集合的一端(称为堆栈顶端,top)进行加入数据(push)和移除数据(pop)的运算。因而按照后进先出(LIFO, Last In First Out)的原理运作。

有点难懂?没关系,我们来看一张图: 

没错,桌上的这一叠盘子就是一个栈结构(实际上还需要加一些限定条件,一会会讲到)。其实我们每天都会与“栈”接触。可以说只要理解了这一叠盘子,你就理解了栈!

首先根据维基百科的专业定义,栈有两种基本操作:弹栈和压栈:

图片

对应到我们的这一叠盘子,通常情况下也是两个操作:取盘子和放盘子:

图片

还记得我们上文说过这一叠盘子如果要成为栈,还需要一些限定条件吗?下面我们就来看一下这个条件:

上述对盘子的所有操作必须在盘子顶进行!

说得更直白点,就是取盘子的时候只能从顶部取,不能从中间取;放盘子的时候只能放在顶部,不能插到中间。

于是我们发现,我们每次从这一叠盘子里取出的盘子都是最后叠上去的,最先叠上去的只有在上面的盘子全部取完以后才能取出来。而上面这句话,就是栈结构的最大特点:

后进先出(LIFO, Last In First Out)

OK。以上就是认识栈结构所需要的所有知识,是不是很简单。

图片

下面我们再用一组图片深入理解一下栈结构与其操作过程:

图片

这是一个单向开口的空间,所有元素都通过顶部的开口进入和弹出,内部的橙色方块就是进栈的元素。很明显,先进入的元素会被后进入的元素盖住,取数据的时候只能先取后进入的数据。此时最上面的元素C就被称之为栈顶,最下面的元素A自然就成为了栈底

图片

这就是压栈过程,新放入的元素D放在栈的最上面,代替原来的元素C成为了新的栈顶。栈底仍然为A。

图片

这是弹栈(出栈)过程,元素D被取出,元素C再次成为栈顶,元素A依然是栈底,并且只有等到A上面的所有元素(B、C)全部取出时,A才有机会出栈。

图片

除了压栈和弹栈这两个基本操作,栈结构还有两个特殊状态。此处停止下滑,请思考一下是哪两个。

图片

左边的栈中没有任何元素,所以被称之为空栈,右边被填满了元素,因此被称之为满栈。一般情况下,在程序初始化栈的时候要保证栈为空,在后续的压栈与弹栈操作流程中还要在每次操作前检测栈状态,避免在满栈状态下压栈,在空栈的状态下弹栈,一旦出现上面两种情况,就会导致数据溢出或访问到非法数据,轻则导致程序崩溃死机,重则引起设备失控,甚至出现伤人事件!这并不是危言耸听,试想如果一架运行着的飞机突然自动控制单元出现了栈溢出,导致控制程序死机,无法被控制,也无法切换手动运行。后果自然是非常严重的!

说了这么多,好像栈是一个很简单,且很不灵活的结构,那这玩意儿到底有啥用?

图片

可不要小看了这个结构,在如今的计算机科学、编程与算法中,栈是非常重要,也是用得非常多的一种数据结构,下面我们举几个例子:

  • 函数调用:在计算机程序中,函数的调用和返回借助栈来实现。每次调用函数时,函数的参数和局部变量都会被存储在栈中,当函数执行完成后,栈会弹出这些数据,返回调用点。

  • 表达式求值:栈可以用于解析和求值表达式,包括中缀表达式转换为后缀表达式以及后缀表达式的求值。

  • 内存管理:栈用于存储函数的局部变量和临时数据,对内存的分配和释放起到了关键作用。

  • 括号匹配:栈可以用于检查括号匹配,例如检查一个字符串中的括号是否正确闭合。

  • 后退和撤销:在许多应用程序中,栈可以用于实现后退和撤销功能,例如文本编辑器中的撤销操作。

上面这些例子都是我们日常生活中频繁遇到的场景,由此可见栈无处不在。比如就在写这段话的时候,我还撤销了一个错别字。

图片

到这里你以为就结束了吗?NO! NO! NO! 作为实践主义的一员,我深信实践是检验真理的唯一标准,没有实践,怎么能说理解!接下来,我们一起来用 C 语言实现一个栈结构!

图片

发车之前,我们先明确一点,栈在软件上一般有数组和链表两种实现方式,链表形式会比较复杂,并且涉及到另外的数据结构,本文既然是讲栈的,就尽量不引入其他结构来避免理解困难,因此下文会实现一个纯数组栈。

首先我们来定义一个最简单的栈结构:

#define MAX_SIZE 100  // 栈的最大容量typedef struct {int data[MAX_SIZE];int top;
} Stack;

可以看到,这个结构里有一个数组和一个变量。其中 data 数组就是栈容器,用于存储数据,而 top 变量就用于指示当前栈顶的栈顶计数(有些地方会用一个指针,即栈顶指针。这里直接用计数比较好理解,作用是一样的)。

定义完结构后我们来进行初始化:

// 初始化栈
void init(Stack *stack) { stack->top = -1; }

这里的初始化逻辑很简单,就是将 top 变量的值设置为 -1 。在后续的程序中只要这个值为 -1,我们就认为当前栈为空:

// 判断栈是否为空
bool isEmpty(Stack *stack) { return stack->top == -1; }

自然,当 top 值为 MAX_SIZE - 1 时,就代表当前栈已满(有一个元素时值为0):

// 判断栈是否已满
bool isFull(Stack *stack) { return stack->top == MAX_SIZE - 1; }

压栈:


// 压栈
void push(Stack *stack, int value) {if (isFull(stack)) {printf("栈已满,无法入栈\n");} else {stack->data[++stack->top] = value;}
}

将数据压栈就是把数据放入数组,同时栈顶计数加一表示当前加入了一个数据。这里要注意,压入数据之前必须要检查栈是否满,避免栈溢出。

弹栈(出栈):

// 出栈
int pop(Stack *stack, int *value) {if (isEmpty(stack)) {printf("栈已空,无法出栈\n");return -1;} else {*value = stack->data[stack->top--];return 0;}
}

弹栈则是对外弹出(返回)数组内有效数据的顶部数据,同时,栈顶计数减一表示当前取出了一个数据。同样,必须确保栈不为空再进行弹栈操作。

查看栈顶元素:

// 获取栈顶元素
int top(Stack *stack, int *value) {if (isEmpty(stack)) {printf("栈已空,无栈顶元素\n");return -1;} else {*value = stack->data[stack->top];return 0;}
}

该操作和弹栈很类似,但必须要注意,该操作不会导致栈顶计数的变化,只是查看元素。可以理解为,你小时候每次走过玩具店都要看一眼橱窗,然后对妈妈说:我就看看,不买~

打印栈中的元素:

// 打印栈中的元素
void printStack(Stack *stack) {printf("栈中的元素为:");for (int i = 0; i <= stack->top; i++) {printf("%d ", stack->data[i]);}printf("\n");
}

调试接口,从栈顶到栈底依次打印出栈内数据,用于查看栈内的数据状态。

最后我们用一个 main 函数将上述接口都串起来,形成一个可以运行的程序:

int main() {int ret = -1, value = 0;Stack stack;init(&stack);push(&stack, 3);push(&stack, 5);push(&stack, 7);printStack(&stack);ret = pop(&stack, &value);if (ret == 0) {printf("出栈元素为:%d\n", value);} else {printf("出栈失败");}printStack(&stack);return 0;
}

这个程序的运行逻辑如下:

定义一个栈 > 初始化栈 > 将3压入栈 > 将5压入栈 > 将7压入栈 > 打印当前栈状态 > 弹栈 > 打印弹出数据 > 打印当前栈状态。

这里再次停止往下滑,先思考一下输出结果会是什么。下面来看下运行结果:

jay@jaylinuxlenovo:~/test/stack$ ./stack 
栈中的元素为:3 5 7 
出栈元素为:7
栈中的元素为:3 5 

是不是和你想的一样呢?对于栈的概念,你是不是真的理解了呢。下面附上完整代码,感兴趣的小伙伴可以自己尝试运行一下。

/**************************************************************** @file           stack.c* @brief* @author         WKJay* @Version* @Date           2023-12-07***************************************************************/
#include <stdio.h>
#include <stdbool.h>#define MAX_SIZE 100  // 栈的最大容量typedef struct {int data[MAX_SIZE];int top;
} Stack;// 初始化栈
void init(Stack *stack) { stack->top = -1; }// 判断栈是否为空
bool isEmpty(Stack *stack) { return stack->top == -1; }// 判断栈是否已满
bool isFull(Stack *stack) { return stack->top == MAX_SIZE - 1; }// 压栈
void push(Stack *stack, int value) {if (isFull(stack)) {printf("栈已满,无法入栈\n");} else {stack->data[++stack->top] = value;}
}// 出栈
int pop(Stack *stack, int *value) {if (isEmpty(stack)) {printf("栈已空,无法出栈\n");return -1;} else {*value = stack->data[stack->top--];return 0;}
}// 获取栈顶元素
int top(Stack *stack, int *value) {if (isEmpty(stack)) {printf("栈已空,无栈顶元素\n");return -1;} else {*value = stack->data[stack->top];return 0;}
}// 打印栈中的元素
void printStack(Stack *stack) {printf("栈中的元素为:");for (int i = 0; i <= stack->top; i++) {printf("%d ", stack->data[i]);}printf("\n");
}int main() {int ret = -1, value = 0;Stack stack;init(&stack);push(&stack, 3);push(&stack, 5);push(&stack, 7);printStack(&stack);ret = pop(&stack, &value);if (ret == 0) {printf("出栈元素为:%d\n", value);} else {printf("出栈失败");}printStack(&stack);return 0;
}

到站,下车!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/852730.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言结构体内嵌接口

前言 在golang中&#xff0c;结构体内嵌结构体&#xff0c;接口内嵌接口都很常见&#xff0c;但是结构体内嵌接口很少见。它是做什么用的呢&#xff1f; 当我们需要重写实现了某个接口的结构体的(该接口)的部分方法&#xff0c;可以使用结构体内嵌接口。 作用 继承赋值给接口…

信号与系统实验MATLAB-实验1-信号的MATLAB表示及信号运算

实验1-信号的MATLAB表示及信号运算 一、实验目的 1、掌握MATLAB的使用&#xff1b; 2、掌握MATLAB生成信号波形&#xff1b; 3、掌握MATLAB分析常用连续信号&#xff1b; 4、掌握信号运算的MATLAB实现。 二、实验内容 编写程序实现下列常用函数&#xff0c;并显示波形。…

PyTorch -- Visdom 快速实践

安装&#xff1a;pip install visdom 注&#xff1a;如果安装后启动报错可能是 visdom 版本选择问题 启动&#xff1a;python -m visdom.server 之后打开出现的链接 http://localhost:8097Checking for scripts. Its Alive! INFO:root:Application Started INFO:root:Working…

数据网格和视图入门

WinForms数据网格&#xff08;GridControl类&#xff09;是一个数据感知控件&#xff0c;可以以各种格式&#xff08;视图&#xff09;显示数据。本主题包含以下部分&#xff0c;这些部分将指导您如何使用网格控件及其视图和列&#xff08;字段&#xff09;。 Grid Control’s…

BUUCTF-Web题目1

目录 [HCTF 2018]admin 1、题目 2、知识点 3、思路 [极客大挑战 2019]BuyFlag 1、题目 2、知识点 3、思路 [HCTF 2018]admin 1、题目 2、知识点 BP暴力破解密码 3、思路 打开题目&#xff0c;查看页面源代码&#xff0c;发现需要admin用户才可以登录 这一台有很多解法…

LeetCode | 20.有效的括号

这道题就是栈这种数据结构的应用&#xff0c;当我们遇到左括号的时候&#xff0c;比如{,(,[&#xff0c;就压栈&#xff0c;当遇到右括号的时候&#xff0c;比如},),]&#xff0c;就把栈顶元素弹出&#xff0c;如果不匹配&#xff0c;则返回False&#xff0c;当遍历完所有元素后…

K8s 卷快照类

卷快照类 卷快照类 这个警告信息通常出现在使用 kubectl 删除 Kubernetes 集群资源时&#xff0c;如果尝试删除的是集群作用域&#xff08;cluster-scoped&#xff09;的资源&#xff0c;但指定了命名空间&#xff08;namespace&#xff09;&#xff0c;就会出现这个警告。 集…

基于PointNet / PointNet++深度学习模型的激光点云语义分割

一、场景要素语义分割部分的文献阅读笔记 1.1 PointNet PointNet网络模型开创性地实现了直接将点云数据作为输入的高效深度学习方法&#xff08;端到端学习&#xff09;。最大池化层、全局信息聚合结构以及联合对齐结构是该网络模型的三大关键模块&#xff0c;最大池化层解决了…

72、AndroidStudio 导入项目Connect timed out错误解决

一、背景&#xff1a; 开发过程中难免会 clone 其他的项目&#xff0c;clone 或者下载成功之后。使用 android studio 打开项目时经常遇到 Connect timed out错误如图所示&#xff1a; 二、分析原因&#xff1a; 1、既然链接超时&#xff0c;肯定是 android studio 在运行…

包装类的应用

一.什么是包装类 基本数据类型所对应的引用数据类型 二.集合中不能存储基本数据类型 三.JDK5以后对包装类新增了什么特性&#xff1f; // 自动装箱:把基本数据类型会自动的变成对应的包装类 // 自动拆箱:把包装类自动的变成其对象的基本数据类型 四.我们以后如何获取包…

02-MybatisPlus批量插入性能够吗?

1 前言 “不要用 mybatis-plus 的批量插入&#xff0c;它其实也是遍历插入&#xff0c;性能很差的”。真的吗&#xff1f;他们的立场如下&#xff1a; 遍历插入&#xff0c;反复创建。这是一个重量级操作&#xff0c;所以性能差。这里不用看源码也知道&#xff0c;因为这个和…

数据结构:手撕代码——顺序表

目录 1.线性表 2.顺序表 2.1顺序表的概念 2.2动态顺序表实现 2.2-1 动态顺序表实现思路 2.2-2 动态顺序表的初始化 2.2-3动态顺序表的插入 检查空间 尾插 头插 中间插入 2.2-4 动态顺序表的删除 尾删 头删 中间删除 2.2. 5 动态顺序表查找与打印、销毁 查找 …

mysql导入sql文件失败及解决措施

1.报错找不到表 1.1 原因 表格创建失败&#xff0c;编码问题mysql8相较于mysql5出现了新的编码集 1.2解决办法&#xff1a; 使用vscode打开sql文件ctrlh&#xff0c;批量替换&#xff0c;替换到你所安装mysql支持的编码集。 2.timestmp没有设置默认值 Error occured at:20…

一个公用的数据状态修改组件

灵感来自于一项重复的工作&#xff0c;下图中&#xff0c;这类禁用启用、审核通过不通过、设计成是什么状态否什么状态的场景很多。每一个都需要单独提供接口。重复工作还蛮大的。于是&#xff0c;基于该组件类捕获组件跳转写了这款通用接口。省时省力。 代码如下&#xff1a;…

Linux开机自启/etc/init.d和/etc/rc.d/rc.local

文章目录 /etc/init.d和/etc/rc.d/rc.local的区别/etc/init.dsystemd介绍 /etc/init.d和/etc/rc.d/rc.local的区别 目的不同&#xff1a; /etc/rc.d/rc.local&#xff1a;用于在系统启动后执行用户自定义命令&#xff0c;适合简单的启动任务。 /etc/init.d&#xff1a;用于管理…

实现一个vue js小算法 选择不同的时间段 不交叉

以上图片选择了时间段 现在需要判断 当前选择的时间段 不能够是 有交叉的所以现在需要循环判断 //判断时间段是否重叠交叉 export function areIntervalsNonOverlapping(intervals:any) {// 辅助函数&#xff1a;将时间字符串转换为从当天午夜开始计算的分钟数function conver…

信息系统架构风格-系统架构师(十

1、信息系统架构风格是描述特定应用领域中系统组织方式的惯用模式。架构风格定义了一个系统家族&#xff0c;即一个架构定义&#xff08;&#xff09;。 A一组设计原则 B一组模式 C一个词汇表和一组约束 D一组最佳实践 解析&#xff1a; 信息系统架构风格是描述某一特定 应…

汽车金属管检测新方法,分度盘高速视觉检测机检测效果如何?

汽车金属管是指在汽车制造和维修中广泛使用的金属管道&#xff0c;用于传输流体、气体或其他介质。汽车金属管在汽车中扮演着重要的角色&#xff0c;用于传输液体&#xff08;如燃油、冷却液、润滑油&#xff09;、气体&#xff08;如空气、排气&#xff09;、制动系统、液压系…

详解函数动态调用的作用——call

动态调用的作用 类似于其他语言的反射能够开发框架性代码 Call调用语法 (bool success, bytes data) <address>.call(bytes calldata)call是address的方法call返回值(bool success, bytes data)忽视返回值success&#xff0c;会造成严重问题 calldata的结构 call的…

183.二叉树:二叉搜索树中的众数(力扣)

代码解决 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* Tre…