数据结构-栈(理解版)
一、栈的定义
相信大家对于栈或多或少有一些了解,可能大多数人会告诉你栈是一种先进后出的数据结构。这其实说了跟没说一样(❁´◡`❁)!当然(last in,first out)是栈最有特色的性质。
这里可以给大家一些比较好理解的例子,像男生对枪械感兴趣的,不难发现:弹夹其实就是一个先进后出的容器,压入子弹后,先打出去的一定是最后压入的。同理,第一枚压入的子弹一定是最后打出的。

再比如,我们在浏览网页时,经常会有回退(即回到上一个浏览的网页)这一操作,那么同样不难想象,第一次回退操作的结果肯定是回到,除当前页面最后浏览的,而并不是第一个浏览的网页,这同样是先进后出的典型代表。

接下来我们展开讲一讲怎么将后进先出抽象出来。这里我们先回顾一下线性表的插入和删除操作,先找到插入位置的前驱结点,然后做指针或数组下标的相关操作。那么栈只不过是将操作对象局限为最后一个结点罢了。
那么其实总结起来说,栈还就只是一种后进先出的容器罢了,更简单明了地说,栈的本质还是一种顺序表,不过是只能在其一端进行插入和删除操作的特殊线性表。
那么就会有很多人问,既然栈的本质还是一种线性表,那我们为什么还要去重新定义这样一种数据结构呢?直接在线性表的基础上进行数组下标或者是链表指针的操作不就行了。首先我想说这是一个好问题,因为我也有这样的疑问。但是大家想一下,我们既然有了数组,为什么还要有顺序表呢?因为大家清楚顺序表本质就是一个数组。其实这就涉及到程序设计的问题,我们当然希望在编写程序的时候关注的问题越少越好,所以栈这个数据结构的创建就使得程序设计时的思考范围变小了,可以不需要去花精力考虑类似于数组下标等问题。
二、理解并手撕栈
头文件及宏定义
#include <stdio.h>
#include <stdlib.h>#define elemtype int
#define initcapacity 10
#define increment 10
initcapacity:初始化栈时的最大容量。
increment:在后面栈满后对空间进行再分配时,栈的最大空间的增量。
栈的定义
(这里以顺序栈为例,后期会更新其他实现:链栈、队列栈)
typedef struct Stack{elemtype *base;elemtype *top;int stacksize;
} Stack;
结构体中定义了两个elemtype类型的指针(*top、*base),然后是栈的最大容量stacksize。其中*top指向栈顶,*base指向栈底,所以由栈的LIFO特性可以知道,所有的操作都是在top一端进行的。
初始化栈(initStack)
Stack* initStack(){Stack *s=(Stack*)malloc(sizeof(Stack));s->base=(elemtype*)malloc(sizeof(elemtype)*initcapacity);s->top=s->base;s->stacksize=initcapacity;return s;
}
初始化时,当然是先要定义结构体Stack *s,然后将结构体中的元素(*top、*base)分配空间,而空间的长度就是宏定义的initcapacity。最后还要更新一下s->stacksize。
(ps:此处可以给s->base分配,再将其赋给s->top,也可以反过来,没有区别)
(ps:函数返回值是*Stack,当然也可以传入*Stack指针然后再进行初始化。)
销毁栈(destroyStack)
void destroyStack(Stack *s){ free(s->base); s->base=NULL; s->top=NULL;s->stacksize=0;
}
这里进行的操作是,先对结构体中的s->base进行free()操作,然后将其指针指置空。最后更新s->stacksize=0。
这里大家可能也会有问题,为什么只free(s->base),不用free(s->top)?
这里要简单解释一下free()函数的机制,我们输入由动态分配的内存的首地址,然后函数会将自定计算这块内存的长度,然后将其设置为可分配状态,最后由操作系统释放掉。而我们这里的首地址是s->base,s->top并不是首地址,所以应该释放掉s->base。
这里可能大家还会有疑惑,既然是将栈销毁,为什么不直接free(s),也就是直接将结构体释放掉,这样难道不是更方便吗?
首先我们要明确销毁栈的目的是什么?应该是,在我不需要这个栈时,我希望这一块空间被释放掉,也就是可被再分配。
那么如果直接将释放结构体,那么在结构体中动态分配的内存(也就是s->base)是无法释放的,这也就会导致内存泄漏,与我们的目的就不符了。
清空栈(clearStack)
void clearStack(Stack *s){s->top=s->base;s->stacksize=0;
}
清空栈比较简单,我只需要将将top指针重置为base就可以了。
(ps:这种方式的清空并不是真正意义上的将栈中元素删除了,它其实是一种清空的假象,栈的物理结构并没有改变,只不过那些没有删去的元素不用再去访问它了,在我要入栈时就会覆盖掉那些元素)
!!!如果有强迫症的话,可以将每一个元素删除 (^///^)。
压栈(pushStack)
void pushStack(Stack *s,elemtype e){if(s->top - s->base >= s->stacksize){s->base=(elemtype*)realloc(s->base,sizeof(elemtype)*(initcapacity+increment));if(!s->base){printf("fail realloc\n");exit(0);}s->top=s->base+s->stacksize;s->stacksize+=increment;}*s->top=e;s->top++;
}
其实压栈的核心操作就是两行代码:
*s->top=e;
s->top++;
另一大坨主要是考虑扩容和代码健壮性的校验代码:
s->base=(elemtype*)realloc(s->base,sizeof(elemtype)*(initcapacity+increment));
值得注意的是如果不熟悉realloc()函数,要注意一下该函数的输入,两个参数,(原内存首地址,再分配后的内存空间总数) ,然后进行强制类型转换。
弹栈(popStack)
void popStack(Stack *s){if(s->top==s->base){printf("stack is empty\n");exit(0);}printf("pop-%d\n",*(--s->top));
}
弹栈的操作比较简单,先判断是否为空栈,然后将顶部元素弹出。这里需要注意的是s->top,并不是头部元素,一般情况下它是空的,等待着要入栈的元素。所以弹出的元素应该是,--s->top指向的元素。
(ps:s->top是一个指针,要输出值的话,是*s->top)
栈长度、顶部元素获取
int lengthStack(Stack *s){return s->top - s->base;
}elemtype GetTop(Stack *s){return *(s->top-1); //时刻记住s->top,是一个指针,要取值的话,加*!!!
}
这里涉及到的知识带是,指针可以相减获得两指针的距离:
s->top - s->base;
同样,这里需要注意栈顶元素是*(s->top-1)。
遍历栈(traverseStack)
void traveraeStack(Stack *s){if(s->base==s->top){printf("Stack is empty!\n");exit(0);}int len=lengthStack(s);for(int i=1;i<=len;++i){printf("%d->",*(s->top-i)); } printf("end\n");
}
这里在遍历的时候也有一个小细节,不能直接写 *--(s->top),因为*s是作为指针传入的,这样会修改结构体中s->top的值,那么遍历结束后s->top就会与s->base重合了!!!此时再进行遍历就会是空栈!!!
运行结果

没有bug!!!
三、写在最后
其实栈的实现逻辑是很简单的,就抓住栈的LIFO特性就可以了,后面的学的队列也是这样。
今天是以顺序栈为例分析的,其实还有链表实现栈,队列实现栈,以及以栈为基础的双端栈,这些数据结构都会之后的博客中,我都会写到。然后文章有什么问题的话,希望大家能够帮忙指正!
然后源代码已经上传到下面了,有需要的可以自取。
顺序栈(源码)
相关文章:
数据结构-栈(理解版)
一、栈的定义 相信大家对于栈或多或少有一些了解,可能大多数人会告诉你栈是一种先进后出的数据结构。这其实说了跟没说一样(❁◡❁)!当然(last in,first out)是栈最有特色的性质。 这里可以给大家一些比较好理解的例…...
NAND Flash虚拟层初始化
在整个NAND Flash初始化过程中,其主要过程由NAND_Init()函数来完成的,因此以下以NAND_Init()函数作为入口,对NAND Flash虚拟层初始化进行全面分析: NAND_Init()NAND_PhyInit()FMT_Init()FMT_FormatNand()LML_Init() NAND_Init()函数首先调用NAND_PhyInit()函数…...
zabbix7.0监控linux主机案例详解
前言 服务端配置 链接: rocky9.2部署zabbix服务端的详细过程 环境 主机ip应用zabbix-server192.168.10.11zabbix本体zabbix-client192.168.10.12zabbix-agent zabbix-server(服务端已配置) 具体实现过程 zabbix-client配置 安装zabbix-agent 添加扩展包 dnf -y instal…...
2024重生之回溯数据结构与算法系列学习(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
欢迎各位彦祖与热巴畅游本人专栏与博客 你的三连是我最大的动力 以下图片仅代表专栏特色 专栏跑道一 ➡️ MYSQL REDIS Advance operation 专栏跑道二➡️ 24 Network Security -LJS 专栏跑道三 ➡️HCIP;H3C-SE;CCIP——LJS[华为、华三、思科高级网络]…...
django drf 过滤器
排序 代码: from rest_framework.generics import ListAPIView from rest_framework.filters import OrderingFilterclass TestListAPIView(ListAPIView):queryset models.Course.objects.filter(is_deleteFalse).all()serializer_class serializers.TestModelS…...
蓝桥杯—STM32G431RBT6(RTC时钟获取时间和日期)
一、RTC是什么,有什么用? 在 STM32 中,RTC(Real-Time Clock,实时时钟)主要有以下作用: 时间保持:即使在系统断电情况下,也能持续记录时间。(需要纽扣电池供电…...
DriveVLM 论文学习
论文链接:https://arxiv.org/abs/2402.12289 解决了什么问题? 自动驾驶对交通行业有着革命性的作用,实现 FSD 的一个主要障碍就是场景理解。场景理解涉及在复杂且不可预测的环境中进行导航,这些环境可能包括恶劣的天气条件、复杂…...
Unity3D 客户端多开
Unity3D 实现客户端多开 客户端多开 最近在做好友聊天系统,为了方便测试,需要再开一个客户端。 简单的方法,就是直接拷贝一个新的项目,但是需要很多时间和占用空间。 查阅了网络资料,发现有一种软链接,…...
使用代理IP数据采集都需要注意那些?
“在当今大数据时代,数据采集成为了企业决策和个人研究的重要依据。然而频繁访问目标网站往往会引发IP被封锁的风险,这时使用代理IP就显得尤为重要。但代理IP的使用并非毫无风险,以下是使用代理IP进行数据采集时需要注意的几个关键事项。” 一…...
城市大脑:智慧城市的神经中枢——典型实践与经验启示
随着信息技术的飞速发展,智慧城市已成为全球城市转型升级的重要方向。“城市大脑”作为智慧城市的核心引擎,正以其强大的数据处理能力、智能决策支持和跨领域协同优势,引领着城市管理与服务的深刻变革。本文将深入探讨几个具有代表性的“城市…...
嵌入式中CW32多功能测试笔实现
前言 起心动念 在日常的硬件调试工作中,我们最常使用的仪器仪表可能就是万用表了,虽然万用表号称“万用”,但大部分时候,我们需要使用到的功能无非是电压测量和通断测量。 作为调试的“得力干将”,万用表有时候也会存在存在一些缺点和局限性,比如:体积较大不便于携带…...
Python 时间占位符:毫秒的使用
Python 时间占位符:毫秒的使用 在 Python 中,处理时间和日期是一个非常常见的任务。在进行时间格式化时,使用占位符来表示特定的时间单位是非常重要的。特别是毫秒(ms),它在许多应用中扮演着关键角色&…...
深度学习:(七)梯度下降法在神经网络中的应用
梯度下降法在神经网络中的应用 事先规定: 用 n n n 表示个数(维度): n [ 0 ] n x n^{[0]}n_x n[0]nx ,表示单个训练样本 x x x 的元素个数; n [ 1 ] n^{[1]} n[1] 表示隐藏层 1 1 1 的单元(节点&am…...
HarmonyOS---权限和http/Axios网络请求
网络请求(http,axios) 目录 一、应用权限管理1.1权限的等级1.2授权方式1.3声明权限的配置1.4如何向用户进行申请 二、内置http请求使用三、Axios请求使用(建议)3.1 使用方式一3.2 使用方式二(建议) 一、应用权限管理 应用权限保护…...
信号量SEM
前提 1.信号量的本质是一把计数器 2.申请信号本质就是预订资源 3.PV操作是原子的! 将一个公共资源当做整体访问-->锁 如果公共资源不当做整体使用,多进程可以并发的访问公共资源,但不是同一个区域,为了将资源均分,所以有了…...
828华为云征文 | 基于华为云Flexus云服务器X搭建部署——AI知识库问答系统(使用1panel面板安装)
🚀对于企业来讲为什么需要华为云Flexus X来搭建自己的知识库问答系统??? 【重塑知识边界,华为云Flexus云服务器X引领开源问答新纪元!】 🌟 解锁知识新动力,华为云Flexus云服务器X携…...
从零预训练一个tiny-llama#Datawhale组队学习Task2
完整的教程请参考:datawhalechina/tiny-universe: 《大模型白盒子构建指南》:一个全手搓的Tiny-Universe (github.com) 这是Task2的学习任务 目录 Qwen-blog Tokenizer(分词器) Embedding(嵌入) RMS …...
【Linux探索学习】第二弹——Linux的基础指令(中)——夯实基础第二篇
Linux基础指令(上):【Linux探索学习】第一弹——Linux的基本指令(上)——开启Linux学习第一篇-CSDN博客 前言: 在前面我们已经讲解了一些常用的Linux的基础指令,那些当然是远远不够的ÿ…...
Python和QT哪个更适合嵌入式方向的上位机开发?
最近因为工作需要,需要做一个上位机用来处理收集到的数据,然后进行分析,最好有图标输出,当然还要考虑开发便捷,毕竟平时主要是嵌入式方向开发,核心技术栈主要是Linux和C语言,对于开始上位机并不…...
Unity实战案例全解析:RTS游戏的框选和阵型功能(5)阵型功能 优化
前篇:Unity实战案例全解析:RTS游戏的框选和阵型功能(4)阵型功能-CSDN博客 本案例来源于unity唐老狮,有兴趣的小伙伴可以去泰克在线观看该课程 我只是对重要功能进行分析和做出笔记分享,并未无师自通&#x…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
大数据驱动企业决策智能化的路径与实践
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:数据驱动的企业竞争力重构 在这个瞬息万变的商业时代,“快者胜”的竞争逻辑愈发明显。企业如何在复杂环…...
高抗扰度汽车光耦合器的特性
晶台光电推出的125℃光耦合器系列产品(包括KL357NU、KL3H7U和KL817U),专为高温环境下的汽车应用设计,具备以下核心优势和技术特点: 一、技术特性分析 高温稳定性 采用先进的LED技术和优化的IC设计,确保在…...
LUA+Reids实现库存秒杀预扣减 记录流水 以及自己的思考
目录 lua脚本 记录流水 记录流水的作用 流水什么时候删除 我们在做库存扣减的时候,显示基于Lua脚本和Redis实现的预扣减 这样可以在秒杀扣减的时候保证操作的原子性和高效性 lua脚本 // ... 已有代码 ...Overridepublic InventoryResponse decrease(Inventor…...
