【数据结构】一篇带你彻底了解栈

news2024/11/24 10:01:38

文章目录

  • 栈的概念及结构
  • 栈接口的实现
    • 栈的初始化
    • 入栈
    • 出栈
    • 获取栈顶元素
    • 判断栈是否为空
    • 获取栈中有效元素个数
    • 栈的销毁
  • 总结

栈的概念及结构

栈:一种线性数据结构,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶 (Top), 另一端称为栈底 [Bottom]。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。即最后进入的元素最先被访问。

  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
  • 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
    在这里插入图片描述
  • 栈具有以下几个特点:
    1. 后进先出(LIFO):最后进入栈的元素最先被访问,而最先进入栈的元素最后被访问。
    2. 只允许在一端进行插入和删除操作:栈通常只允许在栈顶进行插入和删除操作,而不允许在其他位置进行操作。
    3. 栈顶指针:栈顶指针指向栈顶元素,它随着元素的插入和删除而改变。

  • 栈的结构
    对于栈来讲,理论上线性表的操作特性它都具备,可由于它的特殊性,所以针对它在操作上会有些变化。特别是插入和删除操作.那栈的结构用数组还是链表实现?

使用数组可以比链表更快,因为数组的数据在内存中是连续存储的,下标可以随机访问。而链表的数据则是分散存储的。意味着,当CPU访问数组时,它可以利用缓存行(cache line)的特性,从内存中读取多个相邻的元素到缓存中,以便更快地访问这些元素。而对于链表,则需要在内存中跳跃,以获取每个元素的地址,这可能会导致缓存未命中(cache miss),从而降低访问速度。但是数组长度是固定的,如果需要动态扩展栈的大小,需要进行复制和移动操作,比较耗费时间和空间。链表实现的栈可以动态扩展大小,但是需要额外的指针空间来存储链表节点,访问元素时速度可能会比较慢.下面我总结下用数组和链表各自的优缺点。


  • 用数组实现的优点

1 . 数组在内存中是连续存储的,因此访问元素时速度较快。CPU高速缓存命中率会更高。
2. 下标的随机访问。尾插尾删效率不错.
3. 数组实现相对简单,不需要额外的指针来维护元素之间的关系。

  • 数组实现的缺点

1 . 数组的大小是固定的,因此在栈空间不足时需要进行扩容操作,这可能会导致性能下降。
2 . 在删除元素时,需要移动数组中的其他元素,这也可能会导致性能下降。


  • 用链表实现的优点

1 .链表的大小可以动态调整,因此可以更好地利用空间。
2 . 任意位置插入删除O(1) ,链表的性能较高,因为不需要移动其他元素。

  • 链表实现的缺点

1 . CPU高速缓存命中率会更低,不是连续存储的,因此访问元素时速度较慢。
2. 不支持下标的随机访问.

  • 用链表还是用数组结构实现这个问题的答案取决于具体的应用场景和需求,本章节我们使用数组存储结构来实现.
    栈数组存储结构
typedef int STDateType; // 定义栈元素类型
typedef struct StackNode
{
	STDateType * a;  // 数组指针
	int top;		 // 栈顶
	int capacity;	// 栈的容量
}st;

a 是一个指向栈数据存储区的指针,top 是栈顶的索引值,capacity 是栈的容量。


栈接口的实现

栈的初始化

  • 这里讲解下有的人是把top给成-1的,栈顶指针 top 被初始化为 -1,表示栈中没有任何元素。当向栈中插入第一个元素时,我们需要将 top 的值加 1,然后将该元素存储在数组中 top 所指向的位置。此时,top 的值为 0,表示栈中有一个元素。因此,栈中有一个元素是因为插入了第一个元素。所以这里要取分一下.top给成0还是-1,根据场景应用给成多少。
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;	
	pst->top = 0;   // top 指向栈顶数据的下一个位置

	pst->capacity = 0;
}

入栈

  • 入栈步骤
    1. 首先判断栈是否已满,即栈顶指针 top 是否等于栈的容量 capacity。如果栈已满,则需要扩容,这里的扩容策略是将栈的容量翻倍,如果栈的容量为 0,则新分配的容量为 4,将扩容后的数组 tmp 赋值给原来的数组 pst->a,并更新栈的容量为 newCapacity。
    2. 将元素 x 存储在栈顶指针 top 所指向的位置,然后将栈顶指针 top 加 1,指向下一个空闲位置,以便下一次入栈操作。
void STPush(ST* pst, STDataType x)
{
	if (pst->top == pst->capacity) //判断栈是否已满
	{
		int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));//扩容
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}

		pst->a = tmp; //赋值给原来的数组
		pst->capacity = newCapacity; //更新栈的容量
	}

	pst->a[pst->top] = x; //入栈
	pst->top++; //+1 指向下一个空闲位置
}

出栈

  • 判断栈中是否存在元素,如存在我们将栈顶指针 top 减一,即 pst->top–,表示将栈顶元素弹出。
void STPop(ST* pst)
{
	assert(pst); //断言判断是否指针为空
	assert(!STEmpty(pst)); //判断栈中是否存在元素

	pst->top--; //将栈顶元素弹出。
}

获取栈顶元素

  • 首先确保栈不为空,如不为空返回栈顶元素的值,即数组 a 中下标为 top - 1 的元素。
STDataType STTop(ST* pst)
{
	assert(pst);  //断言判断是否指针为空
	assert(!STEmpty(pst)); //判断栈中是否存在元素

	return pst->a[pst->top - 1]; //返回栈顶元素的值
}

判断栈是否为空

  • 如果栈顶值为0,就代表栈为空.返回真,反之返回假.
bool STEmpty(ST* pst)
{
	assert(pst);  //断言判断是否指针为空
	return pst->top == 0; //判断是否为空
}

获取栈中有效元素个数

  • 该代码没有什么可说的…直接看吧
int STSize(ST* pst)
{
	assert(pst);   //断言判断是否指针为空

	return pst->top; //返回栈的元素个数
}

栈的销毁

  • 当我们不打算使用这个栈时,我们需要把它销毁,其实也就是在内存中将它释放掉,以便于留出空间给其他程序或软件使用。 我们只需要把栈的数组空间释放,避免内存泄漏。同时将指向数组的指针 a 置为 NULL,避免出现野指针。最后,将栈的容量 capacity 和栈顶指针 top 置为 0,表示栈已经被销毁。
void STDestroy(ST* pst)
{
	assert(pst); //断言判断是否指针为空

	free(pst->a); //函数释放栈的数组空间
	pst->a = NULL; //置为NULL,避免出现野指针
	pst->top = pst->capacity = 0;  //将栈的容量 capacity 和栈顶指针 top 置为 0
}

总结

  • 栈是一种简单但非常有用的数据结构,掌握它的基本概念、操作和实现方式对于编程学习和刷题,后面的面试都非常重要。

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

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

相关文章

Node.js 学习系列(三) —— REPL

Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Windows 系统的终端或 Unix/Linux shell,可以在终端中输入命令,并接收系统的响应。 Node 自带了交互式解释器,可以执行以下任务: 读取 —…

Spring 初始导读

1.Spring初始 1. 为什么要学框架 学习框架相当于从"小作坊"到"工厂"的升级 , 小作坊什么都要做 , 工厂是组件式装配 , 特点就是高效. 2.框架的优点展示(SpringBoot Vs Servlet) 使用SpringBoot 项目演示框架相比 Servlet 所具备的以下优点: 无需配置 …

Three.js 实战【1】—— 3D全景视图开发

Three.js 实战【1】—— 3D全景视图开发 摘要 1、3D视图Demo2、准备工作——搭建好一个开发环境3、RGBELoader——高范围动态图像加载器4、HDR——高动态范围图像5、使用HDR实现3D全景视图6、直接通过图片纹理进行实现 摘要 在现代开发过程当中,3D开发是越来越不可…

find,which,whereis,grep,bc,uname,free,nano,history指令的语法,功能与选项等

Tips x86_64 与 x64 都是指 64位x86 指的是 32位 find指令的语法,功能与选项 语法:find 目录 -name 文件名功能:在指定的目录之下进行文件查找(递归式查找)选项:它的选项也非常多,讲个 -name…

【Java 基础】File IO流

文章目录 1. File1.1 File类概述和构造方法1.2 绝对路径和相对路径1.3 File 类的常用方法1.4 递归删除文件夹及其下面的文件 2. IO2.1 分类2.2 字节输出流2.3 字节输入流2.4 文件的拷贝2.5 文件拷贝效率优化2.6 释放资源2.7 缓冲流2.8 编码表 3. commons-io 工具包3.1 API 1. F…

VSCode中安装GPT插件详细教程+gpt4改进

目录 安装插件 A.安装CodeGPT B.安装chatgpt 1.VSCode安装插件,使用本地下载vsix文件 2.获取 ChatGPT API 密钥 3.配置settings.json gpt4和3.5对比 GPT-4主要有三大改进点 局限性 安装插件 AB功能一样,A安装的人最多,GPT具体功能可…

K8s之标签、Node选择器与亲和性详解

文章目录 一、标签1、标签是什么?2、给Pod打标签3、给Node节点打标签4、查看标签资源 二、Node选择器1、nodeName(指定Pod调度到指定Node节点)2、nodeSelector(指定Pod调度到具有指定标签的Node节点) 三、亲和性1、Node亲和性-nodeAffinity2、Pod亲和性-podAffinity…

软件测试项目实战经验附视频以及源码【医疗项目,社区管理,前后端分离项目,银行项目,金融项目,车载项目】

前言: ​​大家好,我是测试小马。 很多初学的测试小白都在烦恼找不到合适的项目去练习,这也是难倒大部分测试小白的一个很常见的问题,项目经验确实是每一个测试非常宝贵的经验!这里小马哥给大家找了一些常用的项目合…

AJ-Captcha验证码使用教程源码解读

1.背景 验证码的主要作用是防止机器人恶意使用我们的程序........ 今天我们结束一款强大的验证码组件:aj-captcha 官方文档:AJ-Captcha在线体验 大家一定要认真阅读官方文档 2.项目启动与快速测试 启动后端: 快速页面测试: 使用浏览器访问这个页面 有修改后端源码的情况…

MySQL触发器Trigger加载以及目前局限

GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。作者: 亮文章来源:GreatSQL社区原创 概念介绍 首先需要知道MySQL中触发器特点,以及表table…

掌握这些技巧,让你的Facebook文案更具说服力!

面对广告瀑布流般的竞争,如何让自己的Facebook广告脱颖而出,吸引到用户的眼球,成为广告运营人员必须思考的问题。在这个过程中,文案的作用是至关重要的。 优秀的文案不仅可以吸引用户点击,还能让用户产生共鸣&#xf…

K8s scheduler 调度:预选和优选策略

1 环境准备 kube-scheduler是k8s的核心组件之一,主要负责Pod的调度。scheduler通过监听kube-apiserver,查询未分配 Node的Pod,根据配置的调度策略,将Pod调度到最优的工作节点上,从而高效、合理地利用k8s集群资源。 在m…

shell之数组

一. 关于数组的命令 1. 定义数组 数组名(value0 value1 value2 …) arr(元素1 元素2 元素3 ...) echo ${arr[]}数组名([0]value [1]value [2]value…" arr ([下标1]值1 [下标2]值2 ....) echo ${array3[]}列表名"value0 value1 value2 list"值1 值2 值3 ..…

一文解读spring中事务管理

目录 声明式事务概念 事务基本概念 ①什么是事务 ②事务的特性 编程式事务 声明式事务 基于注解的声明式事务 准备工作 测试无事务情况 加入事务 Transactional的使用 事务属性:只读 事务属性:超时 事务属性:回滚策略 事务属性…

华为OD机试真题 Java 实现【寻找链表的中间结点】【2023Q1 100分】

一、题目描述 给定一个单链表 L,请编写程序输出 L中间结点保存的数据。如果有两个中间结点,则输出第二个中间结点保存的数据。 例如: 给定 L 为 1 -> 7 -> 5,则输出应该为 7; 给定 L 为 1 -> 2 -> 3 -> 4&#…

微服务简介,SpringCloud Alibaba Nacos的安装部署与使用,Nacos集成springboot实现服务注册

目录 一.认识微服务 1.0.学习目标 1.1.单体架构 单体架构的优缺点如下: 1.2.分布式架构 分布式架构的优缺点: 1.3.微服务 微服务的架构特征: 1.4.SpringCloud 1.5Nacos注册中心 1.6.总结 二、Nacos基本使用安装部署服务注册 &am…

聊一聊适配器模式

接口不能用?行,我帮你适配 一、概述 适配器模式(Adapter),是23种设计模式中的结构型模式之一;它就像我们电脑上接口不够时,需要用到的拓展坞,起到转接的作用。它可以将新的功能和原…

SCS【26】单细胞细胞间通信第二部分通信网络的系统分析(CellChat)

桓峰基因公众号推出单细胞生信分析教程并配有视频在线教程,目前整理出来的相关教程目录如下: Topic 6. 克隆进化之 Canopy Topic 7. 克隆进化之 Cardelino Topic 8. 克隆进化之 RobustClone SCS【1】今天开启单细胞之旅,述说单细胞测序的前世…

《微服务实战》 第十一章 Spring Cloud Alibaba nacos配置中心

前言 Nacos 提供用于存储配置和其他元数据的 key/value 存储,为分布式系统中的外部化配置提供服务器端和客户端支持。使用 Spring Cloud Alibaba Nacos Config,您可以在 Nacos Server 集中管理你 Spring Cloud 应用的外部属性配置。 Spring Cloud Aliba…

【C++ 入坑指南】(08)循环

文章目录 一、while 循环二、do...while 循环三、for 循环四、嵌套循环五、跳转语句5.1 break 语句5.2 continue 语句5.3 goto 语句 有的时候,可能需要多次执行同一块代码。一般情况下,语句是顺序执行的:函数中的第一个语句先执行&#xff0c…