当前位置: 首页 > news >正文

数据结构-栈(理解版)

一、栈的定义

相信大家对于栈或多或少有一些了解,可能大多数人会告诉你栈是一种先进后出的数据结构。这其实说了跟没说一样(❁´◡`❁)!当然(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特性就可以了,后面的学的队列也是这样。

今天是以顺序栈为例分析的,其实还有链表实现栈,队列实现栈,以及以栈为基础的双端栈,这些数据结构都会之后的博客中,我都会写到。然后文章有什么问题的话,希望大家能够帮忙指正!

然后源代码已经上传到下面了,有需要的可以自取。

顺序栈(源码)

相关文章:

数据结构-栈(理解版)

一、栈的定义 相信大家对于栈或多或少有一些了解&#xff0c;可能大多数人会告诉你栈是一种先进后出的数据结构。这其实说了跟没说一样(❁◡❁)&#xff01;当然&#xff08;last in&#xff0c;first out&#xff09;是栈最有特色的性质。 这里可以给大家一些比较好理解的例…...

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&#xff1b;H3C-SE;CCIP——LJS[华为、华三、思科高级网络]…...

django drf 过滤器

排序 代码&#xff1a; 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是什么&#xff0c;有什么用&#xff1f; 在 STM32 中&#xff0c;RTC&#xff08;Real-Time Clock&#xff0c;实时时钟&#xff09;主要有以下作用&#xff1a; 时间保持&#xff1a;即使在系统断电情况下&#xff0c;也能持续记录时间。&#xff08;需要纽扣电池供电…...

DriveVLM 论文学习

论文链接&#xff1a;https://arxiv.org/abs/2402.12289 解决了什么问题&#xff1f; 自动驾驶对交通行业有着革命性的作用&#xff0c;实现 FSD 的一个主要障碍就是场景理解。场景理解涉及在复杂且不可预测的环境中进行导航&#xff0c;这些环境可能包括恶劣的天气条件、复杂…...

Unity3D 客户端多开

Unity3D 实现客户端多开 客户端多开 最近在做好友聊天系统&#xff0c;为了方便测试&#xff0c;需要再开一个客户端。 简单的方法&#xff0c;就是直接拷贝一个新的项目&#xff0c;但是需要很多时间和占用空间。 查阅了网络资料&#xff0c;发现有一种软链接&#xff0c;…...

使用代理IP数据采集都需要注意那些?

“在当今大数据时代&#xff0c;数据采集成为了企业决策和个人研究的重要依据。然而频繁访问目标网站往往会引发IP被封锁的风险&#xff0c;这时使用代理IP就显得尤为重要。但代理IP的使用并非毫无风险&#xff0c;以下是使用代理IP进行数据采集时需要注意的几个关键事项。” 一…...

城市大脑:智慧城市的神经中枢——典型实践与经验启示

随着信息技术的飞速发展&#xff0c;智慧城市已成为全球城市转型升级的重要方向。“城市大脑”作为智慧城市的核心引擎&#xff0c;正以其强大的数据处理能力、智能决策支持和跨领域协同优势&#xff0c;引领着城市管理与服务的深刻变革。本文将深入探讨几个具有代表性的“城市…...

嵌入式中CW32多功能测试笔实现

前言 起心动念 在日常的硬件调试工作中,我们最常使用的仪器仪表可能就是万用表了,虽然万用表号称“万用”,但大部分时候,我们需要使用到的功能无非是电压测量和通断测量。 作为调试的“得力干将”,万用表有时候也会存在存在一些缺点和局限性,比如:体积较大不便于携带…...

Python 时间占位符:毫秒的使用

Python 时间占位符&#xff1a;毫秒的使用 在 Python 中&#xff0c;处理时间和日期是一个非常常见的任务。在进行时间格式化时&#xff0c;使用占位符来表示特定的时间单位是非常重要的。特别是毫秒&#xff08;ms&#xff09;&#xff0c;它在许多应用中扮演着关键角色&…...

深度学习:(七)梯度下降法在神经网络中的应用

梯度下降法在神经网络中的应用 事先规定&#xff1a; 用 n n n 表示个数&#xff08;维度&#xff09;: n [ 0 ] n x n^{[0]}n_x n[0]nx​ &#xff0c;表示单个训练样本 x x x 的元素个数&#xff1b; n [ 1 ] n^{[1]} n[1] 表示隐藏层 1 1 1 的单元&#xff08;节点&am…...

HarmonyOS---权限和http/Axios网络请求

网络请求(http,axios) 目录 一、应用权限管理1.1权限的等级1.2授权方式1.3声明权限的配置1.4如何向用户进行申请 二、内置http请求使用三、Axios请求使用&#xff08;建议&#xff09;3.1 使用方式一3.2 使用方式二&#xff08;建议&#xff09; 一、应用权限管理 应用权限保护…...

信号量SEM

前提 1.信号量的本质是一把计数器 2.申请信号本质就是预订资源 3.PV操作是原子的! 将一个公共资源当做整体访问-->锁 如果公共资源不当做整体使用&#xff0c;多进程可以并发的访问公共资源&#xff0c;但不是同一个区域&#xff0c;为了将资源均分&#xff0c;所以有了…...

828华为云征文 | 基于华为云Flexus云服务器X搭建部署——AI知识库问答系统(使用1panel面板安装)

&#x1f680;对于企业来讲为什么需要华为云Flexus X来搭建自己的知识库问答系统&#xff1f;&#xff1f;&#xff1f; 【重塑知识边界&#xff0c;华为云Flexus云服务器X引领开源问答新纪元&#xff01;】 &#x1f31f; 解锁知识新动力&#xff0c;华为云Flexus云服务器X携…...

从零预训练一个tiny-llama#Datawhale组队学习Task2

完整的教程请参考&#xff1a;datawhalechina/tiny-universe: 《大模型白盒子构建指南》&#xff1a;一个全手搓的Tiny-Universe (github.com) 这是Task2的学习任务 目录 Qwen-blog Tokenizer&#xff08;分词器&#xff09; Embedding&#xff08;嵌入&#xff09; RMS …...

【Linux探索学习】第二弹——Linux的基础指令(中)——夯实基础第二篇

Linux基础指令&#xff08;上&#xff09;&#xff1a;【Linux探索学习】第一弹——Linux的基本指令&#xff08;上&#xff09;——开启Linux学习第一篇-CSDN博客 前言&#xff1a; 在前面我们已经讲解了一些常用的Linux的基础指令&#xff0c;那些当然是远远不够的&#xff…...

Python和QT哪个更适合嵌入式方向的上位机开发?

最近因为工作需要&#xff0c;需要做一个上位机用来处理收集到的数据&#xff0c;然后进行分析&#xff0c;最好有图标输出&#xff0c;当然还要考虑开发便捷&#xff0c;毕竟平时主要是嵌入式方向开发&#xff0c;核心技术栈主要是Linux和C语言&#xff0c;对于开始上位机并不…...

Unity实战案例全解析:RTS游戏的框选和阵型功能(5)阵型功能 优化

前篇&#xff1a;Unity实战案例全解析&#xff1a;RTS游戏的框选和阵型功能&#xff08;4&#xff09;阵型功能-CSDN博客 本案例来源于unity唐老狮&#xff0c;有兴趣的小伙伴可以去泰克在线观看该课程 我只是对重要功能进行分析和做出笔记分享&#xff0c;并未无师自通&#x…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词

Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid&#xff0c;其中有多少个 3 3 的 “幻方” 子矩阵&am…...

10-Oracle 23 ai Vector Search 概述和参数

一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI&#xff0c;使用客户端或是内部自己搭建集成大模型的终端&#xff0c;加速与大型语言模型&#xff08;LLM&#xff09;的结合&#xff0c;同时使用检索增强生成&#xff08;Retrieval Augmented Generation &#…...

TSN交换机正在重构工业网络,PROFINET和EtherCAT会被取代吗?

在工业自动化持续演进的今天&#xff0c;通信网络的角色正变得愈发关键。 2025年6月6日&#xff0c;为期三天的华南国际工业博览会在深圳国际会展中心&#xff08;宝安&#xff09;圆满落幕。作为国内工业通信领域的技术型企业&#xff0c;光路科技&#xff08;Fiberroad&…...

手机平板能效生态设计指令EU 2023/1670标准解读

手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读&#xff0c;综合法规核心要求、最新修正及企业合规要点&#xff1a; 一、法规背景与目标 生效与强制时间 发布于2023年8月31日&#xff08;OJ公报&…...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制

目录 节点的功能承载层&#xff08;GATT/Adv&#xff09;局限性&#xff1a; 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能&#xff0c;如 Configuration …...