【深入解析:数据结构栈的魅力与应用】

news2024/9/25 9:37:50

本章重点

  • 栈的概念及结构

  • 栈的实现方式

  • 数组实现栈接口

  • 栈面试题目

  • 概念选择题

一、栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶

出栈:栈的删除操作叫做出栈。出数据也在栈顶

  • 栈顶Top:线性表允许插入和删除的那一端。
  • 栈底Bottom:固定的,不允许进行插入和删除的另一端。
  • 空栈:不含任何元素的空表。

二、栈的实现方式

        由于栈只能在栈顶操作,所以栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。单链表的尾插需要遍历结点,这里使用带头双向循环链表也可,但是空间销毁大。单链表也可以实现,我们就需要让栈顶是头结点,栈底是尾结点,这样入栈就是头插,效率高。

1.顺序堆栈


        根据前边的分析可知,顺序堆栈和顺序表的数据成员(元素)是相同的,不同之处是,顺序堆栈的入栈和出栈操作只能对当前栈顶元素进行。
        顺序堆栈的存储结构如图3-2所示。其中,a0,a1,a2,表示顺序堆栈要存储的元素序列,stack 表示顺序堆栈存放元素的数组,capacity 表示顺序堆栈数组stack的内存单元容量(表示目前允许存储的元素最大个数),top表示顺序堆栈数组stack的当前栈顶位置。

2.链式堆栈

        堆栈有两端,插入元素和删除元素的一端为栈顶,另一端为栈底。对链式堆栈来说,显然,若把靠近头指针的一端定义为栈顶,则插入元素和删除元素时不需要遍历整个链,其时间复杂度为0(1);若把远离头指针的一端定义为栈顶,则每次插入元素和删除元素时都需要遍历整个链,其时间复杂度为0(n)。因此,链式堆栈都设计成把靠近头指针的一端定义为栈顶。链式堆栈的头结点对操作实现的影响不大,因此可有可无。依次向带头结点链式堆栈输入a0,a1,a2,…….an-1后,带头结点链式堆栈的结构示意图如图3-3所示。

三、数组实现栈接口

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
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* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回0,如果不为空返回非零结果
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);

1.初始化栈:void StackInit(Stack* ps)

初始化这里我们先不设置容量,后续入栈再申请空间即可,这里需要注意top的值,我们设置的值是0,它是表示非空栈中的栈顶指针始终在栈顶元素的下一个位置上。

// 初始化栈
void StackInit(Stack* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//表示栈顶元素的下一个位置
	ps->capacity = 0;
}

2.入栈:void StackPush(Stack* ps, STDataType data)

满栈:当我们使一个元素入栈的之前,我们往往需要判断一下栈是否为满栈,防止发生上溢的情况。因为我们定义了一个capacity来表示当前已经分配的存储空间,所以我们可以用ps->top == ps->capacity 来判断当前使用的栈空间是否满了。所以当ps->top == ps->capacity时表示已经满了。

满栈我们要首先追加存储空间,然后才能将元素入栈。realloc()函数可以申请空间,如果realloc第一个参数为空,那么realloc的功能就类似于malloc,这也是为什么我们前面初始化没有开辟空间的原因,写在这里能省大量的代码。

// 入栈
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		int newCapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
		//如果ps->a == NULL,功能相当与malloc
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newCapacity;
	}
	ps->a[ps->top] = data;
	ps->top++;
}

3.出栈:void StackPop(Stack* ps)

出栈时我们首先要判断栈是否为空栈,由于是顺序栈,我们只需要判断ps->top > 0即可判断当前栈的情况。

// 出栈
void StackPop(Stack* ps)
{
	assert(ps);
	//保证不为空
	assert(ps->top > 0);
	ps->top--;
}

4.获取栈顶元素:STDataType StackTop(Stack* ps)

取栈顶元素同样需要判断是否为空栈,空栈也不能取出任何数据,所以这里强制断言,一旦为空栈直接程序报错

// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
	assert(ps);
	//保证不为空
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

5.获取栈中有效元素个数:int StackSize(Stack* ps)

由于top是非空栈中的栈顶指针始终在栈顶元素的下一个位置上,所以top就是当前栈中有效元素个数

// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);

	return ps->top;
}

6.检测栈是否为空,如果为空返回0,如果不为空返回非零结果:int StackEmpty(Stack* ps)

// 检测栈是否为空,如果为空返回0,如果不为空返回非零结果
int StackEmpty(Stack* ps)
{
	if (ps->top != 0)
		return ps->top;
	else
		return 0;
}

7.销毁栈:void StackDestroy(Stack* ps)

销毁栈只需将申请的空间释放,再把设置的大小和容量设施为0即可。

// 销毁栈
void StackDestroy(Stack* ps)
{
	assert(ps);

	free(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

四、栈面试题目

括号匹配问题。OJ链接

        当开始接触题目时,我们会不禁想到如果计算出左括号的数量,和右括号的数量,如果每种括号左右数量相同,会不会就是有效的括号了呢?

        事实上不是的,假如输入是 [ { ] },每种括号的左右数量分别相等,但不是有效的括号。这是因为结果还与括号的位置有关。

        仔细分析我们发现,对于有效的括号,它的部分子表达式仍然是有效的括号,比如 { ( )[ ) ] } 是一个有效的括号,( )[ { } ] 是有效的括号,[ ( ) ] 也是有效的括号。并且当我们每次删除一个最小的括号对时,我们会逐渐将括号删除完。比如下面的例子。

这个思考的过程其实就是栈的实现过程。因此我们考虑使用栈,当遇到匹配的最小括号对时,我们将这对括号从栈中删除(即出栈),如果最后栈为空,那么它是有效的括号,反之不是。

bool isValid(char* s) 
{
	Stack st;
	StackInit(&st);
	char topVal;
	while (*s)
	{
		switch (*s)
		{
		case'(':
		case'[':
		case'{':
			StackPush(&st, *s);
			break;
		case')':
		case']':
		case'}':
			//数量不匹配
			if (!StackEmpty(&st))//栈为空
			{
				StackDestroy(&st);//防止内存泄露
				return false;
			}
			topVal = StackTop(&st);
			StackPop(&st);
			//顺序不匹配
			if (*s == ')' && topVal != '(' 
				|| *s == ']' && topVal != '['
				|| *s == '}' && topVal != '}')
			{
				StackDestroy(&st);//防止内存泄露
				return false;
			}
			break;			
		}
		++s;
	}
	//栈不为空,此时只有右括号,false,说明数量不匹配
	int ret = StackEmpty(&st);
	StackDestroy(&st);
	if (ret == 0)
		return false;
	else
		return true;
}

五、概念选择题

1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是( )。

  • A 、12345ABCDE
  • B 、EDCBA54321
  • C 、ABCDE12345
  • D 、54321EDCBA

元素出栈的顺序遵循后进先出(Last-In-First-Out,LIFO)原则,因此选择B

2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()

  • A 1,4,3,2
  • B 2,3,4,1
  • C 3,1,4,2
  • D 3,4,2,1

A:1入栈后出栈,2,3,4入栈,4出栈,3出栈,2出栈,可能。

B:1,2入栈,2出栈,3入栈,3出栈,4入栈,4出栈,1出栈,可能。

C:1,2,3入栈,3出栈,接下来应该是2出栈或者4入栈,不可能1出栈,不可能。

D:1,2,3入栈,3出栈,4入栈,4出栈,2出栈,1出栈,可能。

本章结束啦!!!

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

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

相关文章

行政区划表设计和多级查询

简介 行政区划的表为一个多层级结构,设计大同小异,大致结构如下所示 其中,code为区划编号(主键),parent_code为父区划编号,ancestors为祖区划编号,查询也主要围绕前两个展开。 查询…

【后端】Core框架版本和发布时间以及.net 6.0启动文件的结构

2023年,第35周,第1篇文章。给自己一个目标,然后坚持总会有收货,不信你试试! .NET Core 是一个跨平台的开源框架,用于构建现代化的应用程序。它在不同版本中有一些重要的区别和发布时间 目录 一、Core版本和…

新研究:Gartner 公有云成本管理框架

2023年6月28日,Gartner 出版了名为《Beyond FinOps: the Gartner Framework for Public Cloud Financial Management》的公有云成本管理框架,旨在帮助企业/组织应对公有云支出的挑战,同时抓住新机遇,推动更有效的 IT 使用。新框架…

【HCIP】05.OSPF邻居与邻接关系

报文 OSPF头部 报文头部中影响邻居关系建立的字段是Router ID、Area ID、AuType HELLO报文 报文中影响建立邻居关系到是 hello time时间,DR优先级等 DD报文 序列号,LSA头部摘要信息I位:第一个报文,用于主从选举M位:…

跨境电商企业新宠Telegram使用指南,这个功能你不能忽视

Telegram是一款消息传递应用,以其隐私和安全性以及作为移动应用的易用性而闻名。截至 2023年 6月,它拥有超过7亿月活跃用户,是全球下载量排名前五的应用之一。随着其受欢迎程度的不断提高,该渠道正在全球企业中得到认可。让我们来…

Apache Doris 入门教程34:Join 优化

Bucket Shuffle Join Bucket Shuffle Join 是在 Doris 0.14 版本中正式加入的新功能。旨在为某些 Join 查询提供本地性优化,来减少数据在节点间的传输耗时,来加速查询。 它的设计、实现和效果可以参阅 上面的图片展示了Bucket Shuffle Join的工作原理…

ES 索引重命名--Reindex(一)

ES reindex脚本流程,下图为整体流程: 步骤(1):每次写入把之前的索引删除再重新创建索引,然后判断索引是否创建成功,由于创建成功返回结果是json,因此用Json Input插件去解析json获得…

(三)行为模式:4、迭代器模式(Iterator Pattern)(C++示例)

1、迭代器模式(Iterator Pattern)含义 迭代器模式(Iterator),提供一种方法顺序访问一个聚合对象中各个元素,而不暴露该对象的内部表示。【DP】 通过使用迭代器模式,可以将遍历算法与集合对象解耦…

pytorch内存泄漏

问题描述: 内存泄漏积累过多最终会导致内存溢出,当内存占用过大,进程会被killed掉。 解决过程: 在代码的运行阶段输出内存占用量,观察在哪一块存在内存剧烈增加或者显存异常变化的情况。但是在这个过程中要分级确认…

IT运维:使用数据分析平台监控 Nginx 服务

Nginx 是一种流行的 HTTP 服务器和反向代理服务器。作为 HTTP 服务器,Nginx 可以非常有效和可靠地提供静态内容;作为反向代理,它可以用作多个后端服务器或其他应用程序(例如缓存和负载平衡)的单个访问入口。 Nginx 同时…

3分钟教你如何选防水劳保鞋

防水劳保鞋是防止水渗透鞋子造成湿漉漉感觉的安全防护鞋,主要用于地面积水或有溅水的作业场景。前面的文章中与大家简单介绍了防水劳保鞋,其实选购防水劳保鞋也是有窍门的。一双质量好的防水劳保鞋可以为工人的工作效率添砖加瓦,反之&#xf…

【2021年遇到最头疼的Bug】【Alibaba中间件技术系列】「RocketMQ技术专题」Broker配置介绍及发送流程、异常(XX Busy)问题分析总结

背景介绍 公司最近年底要对系统做一次大的体检,所以是不测不知道,一测吓一跳啊,出现了很多问题,其中最恶心的问题要数我们的ROCKETMQ消息队列的问题了,大家都知道消息队列是作为流量削峰的主要手段,负责系…

C++基础Ⅰ编译、链接

目录儿 1 C是如何工作的1.1 预处理语句1.2 include1.3 main()1.4 编译单独编译项目编译 1.5 链接 2 定义和调用函数3 编译器如何工作3.1 编译3.1.1 引入头文件系统头文件自定义头文件 3.1.2 自定义类型3.1.3 条件判断拓展: 汇编 3.2 链接3.2.1 起始函数3.2.2 被调用的函数 3.3 …

C++新经典09--函数新特性、inline内联函数与const详解

函数回顾与后置返回类型 函数定义中如果有形参则形参应该有名字,而不光是只有类型,但是如果并不想使用这个形参,换句话说这个形参并不在这个函数中使用,则不给形参名也可以,但在调用这个函数的时候,该位置…

什么情况下,亚马逊账户会被判滥用?

如果说有对亚马逊跨境电商有所了解的朋友就会知道,现在亚马逊跨境电商的规则是十分严格的,亚马逊开店变得越来越困难,尤其是要想成功的把一个亚马逊店铺给开好。 这几年不少有一些违规的亚马逊卖家都被系统检测到了,如果说被系统…

投资者的秘密武器,代理IP在金融决策中的驱动作用

在如今数据为王的时代,无论从事哪个行业,都需要使用数据分析来指导自己的决策,而这些数据又是从哪里来的呢?很多人都知道,数据采集可以帮助我们将分散在互联网各个网站上的大量数据集中起来。对于金融行业来说&#xf…

重磅丨无人机新规出台,这些红线不能踩!

近年来,随着无人机研发技术逐渐成熟,无人机在各个领域得到了广泛应用,包括VR全景航拍、乡村农业、城市管理、环境监测等领域,其应用场景及使用方式都还在迅速拓展,无人机行业受到社会广泛关注。 但在实践中&#xff0c…

SOLIDWORKS焊件是什么?

SOLIDWORKS是一款广泛应用于机械设计领域的三维计算机辅助设计软件。SOLIDWORKS提供了强大的焊件功能,可以帮助工程师们以更高的效率设计焊接件。本文将介绍SOLIDWORKS焊件的概念、特点以及使用方法,以期帮助读者更好地理解和应用这一关键技术。 SOLIDWO…

国际刑警组织逮捕 14 名涉嫌盗窃 4000 万美元的网络罪犯

Bleeping Computer 网站披露,4 月份,国际刑警组织发动了一起为期四个月,横跨 25 个非洲国家的执法行动 “Africa Cyber Surge II”,共逮捕 14 名网络犯罪嫌疑人,摧毁 20000 多个从事勒索、网络钓鱼、BEC 和在线诈骗的犯…

第七次作业 运维高级 docker容器进级版

1、使用mysql:5.6和 owncloud 镜像,构建一个个人网盘。 (1)拉取相应镜像 docker pull mysq:5.6 docker pull owncloud:latest(2)运行mysql:5.6容器 docker run --name mysql -e MYSQL_ROOT_PASSWORD12345 -d mysql:5.6(3)运行owncloud容器 docker run…