【数据结构】C语言实现顺序栈

news2024/11/19 14:46:06

顺序栈的C语言实现

  • 导言
  • 一、栈的分类
  • 二、顺序栈
    • 2.1 顺序栈的数据类型
    • 2.2 顺序栈的初始化
    • 2.3 栈的判空
    • 2.5 顺序栈的进栈
    • 2.6 顺序栈的出栈
    • 2.7 顺序栈的查找
    • 2.8 顺序栈的另一种实现方式
    • 2.9 顺序栈的销毁
  • 结语

封面

导言

大家好,很高兴又和大家见面啦!!!
在上一个篇章中,我们介绍了栈的基本概念,以及栈中的重要术语。通过介绍我们知道了栈的本质也是一种线性表,只不过它是一种操作受限的线性表。因此栈的实现方式与线性表的实现实际上是大同小异的。下面我们就来介绍一下如何通过C语言实现栈。

一、栈的分类

栈作为一种操作受限的线性表,它在存储时根据存储方式的不同,分为两类——顺序栈与链栈。
下面我们将来介绍第一类栈——顺序栈的C语言实现;

二、顺序栈

通过顺序存储的线性表我们称为顺序表,同样,通过顺序存储的栈我们将其称为顺序栈
顺序栈是利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。

2.1 顺序栈的数据类型

地址连续的存储单元相信大家都已经不陌生了。在顺序表中,我们通过数组实现了静态的顺序表,通过malloccalloc函数实现了动态的顺序表。在栈的实现中,我们不妨借鉴顺序表的实现方式来实现栈,因此顺序栈的数据类型我们可以描述为:

//顺序栈的数据类型基本格式
#define MaxSize 10//定义栈中元素的最大个数
typedef struct SqStack {
	ElemType data[MaxSize];//存放在栈中的元素
	int top;//栈顶指针
}SqStack;
//ElemType——存放元素的数据类型
//top——栈顶元素的下标
//SqStack——栈的数据类型

对于顺序栈而言,它的实现就是通过静态数组的方式进行实现的,因此,顺序栈会有一下几个特点:

  1. 栈的大小无法更改;
  2. 进栈操作会受限制,当进栈的元素个数大于栈能存储的元素最大个数时,会出现栈溢出的问题;
  3. 由于进栈操作只能从栈顶进行,因此在实现栈时我们有两种方式实现:
    • 从下标0开始,依次入栈,下标0为栈的栈底;
    • 从下标MaxSize-1开始,依次入栈,下标MaxSize-1为栈的栈底;

接下来我们来看一下顺序栈的初始化;

2.2 顺序栈的初始化

我们在对顺序栈进行初始化时,首先要明确我们要初始化的对象。从数据类型中可知,顺序栈中除了存储元素的静态数组外,还有一个存储栈顶元素下标的栈顶指针。
对于空栈而言,静态数组中存储的内容并不重要,因为我们并不会访问这些内容,因此,我们需要初始化的对象就是顺序栈的栈顶指针。

为了帮助大家更好的理解顺序栈的初始化操作,我们以从下标0为栈底的方式来介绍初始化的实现。

由于栈顶指针指向的是栈中的栈顶元素,存储的是栈顶元素的数组下标,因此,当栈为空栈时,栈顶指针我们只需要将其初始化为-1就行,如下所示:

//顺序栈的初始化
bool InitStack(SqStack* S) {
	if (!S)
		return false;
	S->top = -1;
	return true;
}

由于这里的形参是指针,因此我们在使用前需要对指针进行判空操作,如果指针为空指针时,函数将返回false,当指针不为空指针时,此时我们就可以正常的对栈顶指针进行初始化了;

2.3 栈的判空

我们想知道一个栈是否为空栈时,我们就可以根据栈顶指针的初始化我进行判空,在初始化时,我们将栈顶指针初始化为-1,那么我们在判空时就可以判断此时的栈顶指针是否为-1,如下所示:

//顺序栈的判空操作
bool StackEmpty(SqStack S) {
	if (S.top == -1)
		return true;
	return false;
}

因为我们此时只是判断一下栈的情况,并未对栈有任何的修改,所以我们在传参时,只需要通过传值传参即可,此时的形参只是对实参的一份临时拷贝,我们对形参的任何操作都不会影响实参;

2.5 顺序栈的进栈

当我们创建好一个顺序栈后,我们就可以通过进栈操作来将元素存入顺序栈中,由于空栈时栈顶指针存储的下标为-1,因此我们在存放元素前需要向将栈顶指针指向存放栈顶元素的空间,即对栈顶指针进行+1操作,如下所示:

//顺序栈的入栈操作
bool Push(SqStack* S, ElemType x) {
	//判断指针S是否为空以及栈顶指针是否存满
	if (!S || S->top == MaxSize - 1)
		return false;
	//栈顶指针向上移动
	S->top += 1;
	//将数据存入栈顶
	S->data[S->top] = x;
	return true;
}

为了确保我们能够顺利的将数据存入栈中,我们在进行入栈操作前需要先判断此时的指针S是否为空指针,如果是空指针,那说明传参出现了问题。在确定S不为空指针后,我们还要进一步判断是否为满栈,即栈顶指针存储的下标为MaxSize-1

当然这里我们可以对代码进行一下简化,从实现的顺序我们可以看到,我们是先对栈顶指针进行+1操作,然后再使用的栈顶指针,那也就是先+1再使用,C语言中的前置++这个操作符刚好满足这个特性,因此这里我们就可以将移动与存入合并为一条代码,如下所示:

//顺序栈的入栈操作
bool Push(SqStack* S, ElemType x) {
	//判断指针S是否为空以及栈顶指针是否存满
	if (!S || S->top == MaxSize - 1)
		return false;
	//先移动栈顶指针,再使用
	S->data[++(S->top)] = x;
	return true;
}

在了解了进栈操作后,下面我们来看一下顺序栈是如何进行出栈操作的;

2.6 顺序栈的出栈

不知道大家还记不记得栈的操作特性——后进先出(LIFO),也就是后进栈的元素会先一步出栈,正是因为这个特性,所以我们在进行出栈操作时,只能从栈顶元素开始进行出栈,每次弹出一个元素后,栈顶指针都需要往下移动一位,如下所示:

//顺序栈的出栈操作
bool Pop(SqStack* S, ElemType* x) {
	if (!S || !x || S->top > -1) 
		return false;
	//弹出元素
	*x = S->data[S->top];
	//栈顶指针向下一定
	S->top -= 1;
	return true;
}

出栈操作和入栈操作一样都是需要对栈进行修改,所以这里是通过传址传参完成的出栈,这里有一个点,因为我们要将弹出的元素返回到主函数中,所以对于存储弹出数据的变量x我们也是通过传址的形式进行传参。

同样为了顺利的完成出栈操作,我们需要对指针S与指针x进行判空操作,以确保传参的正确性,同时我们还要确保栈不为空栈。

从出栈的操作顺序我们可以看到,对于栈顶指针,我们是先使用,再对其进行-1的操作,在C语言中后置–这个操作符刚好也是符合这个规则,因此这里我们可以将其改写为:

//顺序栈的出栈操作
bool Pop(SqStack* S, ElemType* x) {
	if (!S || !x || S->top > -1) 
		return false;
	//先使用,后移动栈顶指针
	*x = S->data[S->top--];
	return true;
}

现在我们已经实现了增加、删除的操作,那对于栈的元素我们应该如何查找呢?

2.7 顺序栈的查找

对于栈而言,因为栈的单向操作特性,这就导致我们无法越过栈顶指针去查看栈中存储的其它元素,因此,我们对栈的查找实质上就是对栈顶指针的查找,在找到栈顶指针后将栈顶元素返回给主函数,如下所示:

//顺序栈的查找
bool GetTop(SqStack S, ElemType* x) {
	if (!x || S.top == -1)
		return false;
	//返回栈顶元素
	*x = S.data[S.top];
	return true;
}

对于查找操作而言,因为要带回栈顶元素的具体数据,因此这里对于存储栈顶元素的参数x我们是通过指针进行接收,也就是此时的实参是以传址的方式进行的传参,而且我们在查找操作中并不会修改栈,所以我们只需要对栈有一份临时拷贝就行,可以看到对于形参S,我们是以传值的方式进行传参。

为了能够顺利的进行查找,我们也是需要对指针x与栈顶指针进行判断:

  • 当指针x为空指针时,表示此时传参出现了问题;
  • 当栈顶指针为-1时,表示此时的栈为空栈;

在这两种情况下我们都应该给使用者一个反馈,因此这里就是通过返回false来告知使用者。

下面我们思考一个问题——我们在初始化时能不能将栈顶指针初始化为0呢?答案是可以的。下面我们就来看一下初始化为0时的顺序栈有何改动;

2.8 顺序栈的另一种实现方式

我们在将栈顶指针初始化为0时我们需要先明确此时栈顶指针的含义——栈中已经存储的元素个数,如下图所示:
两种初始化方式
从图中我们可以看到,当栈顶指针初始化为-1时,此时的栈顶指针指向的就是栈顶元素,而当栈顶指针初始化为0时,栈顶指针指向的是栈顶元素上方的空间,在这种情况下操作上面会有以下改动:

  1. 初始化——在初始化时,栈顶指针的值需要有-1改为0;
//顺序栈的初始化
bool InitStack(SqStack* S) {
	if (!S)
		return false;
	S->top = 0;
	return true;
}
  1. 判满——在进行入栈操作前,对栈进行判满操作时由原先的MaxSize-1改为MaxSize;
//顺序栈的入栈操作
bool Push(SqStack* S, ElemType x) {
	//判断指针S是否为空以及栈顶指针是否存满
	if (!S || S->top == MaxSize)
		return false;
}
  1. 入栈——在进行入栈操作时,由原先的先移动栈顶指针,再存入数据改为先存入数据后移动指针;
//顺序栈的入栈操作
bool Push(SqStack* S, ElemType x) {
	//判断指针S是否为空以及栈顶指针是否存满
	if (!S || S->top == MaxSize - 1)
		return false;
	//将数据存入栈顶
	S->data[S->top] = x;
	//栈顶指针向上移动
	S->top += 1;
	//可简写为
	S->data[S->top++] = x;
	return true;
}
  1. 判空——我们在对栈进行判空操作时由原先的判断栈顶指针是否为-1改为栈顶指针是否为0;
//顺序栈的判空操作
bool StackEmpty(SqStack S) {
	if (S.top == 0)
		return true;
	return false;
}
  1. 出栈——在判空完后对栈进行出栈操作时,我们需要将先弹出数据后移动指针改为先移动指针后弹出数据;
//顺序栈的出栈操作
bool Pop(SqStack* S, ElemType* x) {
	if (!S || !x || S->top > -1) 
		return false;
	//栈顶指针向下一定
	S->top -= 1;
		//弹出元素
	*x = S->data[S->top];
	//可简写为
	*x = S->data[--S->top];
	return true;
}
  1. 查找——在对栈顶元素进行查找时,我们需要将直接查找改为先移动指针,再查找;
//顺序栈的查找
bool GetTop(SqStack S, ElemType* x) {
	if (!x || S.top == -1)
		return false;
	//返回栈顶元素
	*x = S.data[--S.top];
	return true;
}

对于第一种初始化为-1的方式,我们在查找栈顶元素时会更加的方便;而第二种初始化为0的方式,我们在进行判空和判满时会对栈中已经存储的元素个数更加的清晰。这两种创建方式各有各的好处,大家可以根据自己的喜好来进行选择,但是一定要注意在进行进栈与出栈操作时的逻辑顺序不要弄反咯。

2.9 顺序栈的销毁

对于栈的销毁,实质上就是将栈中的元素从栈顶依次弹出,最后释放栈的空间,这里我们可以通过循环来完成该操作,如下所示:

//顺序栈的销毁
bool DestroyStack(SqStack* S) {
	if (!S)
		return false;
	while (S->top > -1) {
		S->top--;//栈顶指针往下移动
	}
	return true;
}

当我们初始化是将栈顶指针初始化为0时,对应的循环判断条件我们只需要改成>0就可以了,由于顺序栈是通过静态数组的方式实现的,我们不能像链表以及动态顺序表一样通过free函数来完成销毁操作,只能够在程序完成后有系统自动进行内存回收的操作,这里我就不多加赘述了。

结语

现在对于顺序栈的基本C语言实现我们就全部介绍完了,希望这篇内容能帮助大家更好的学习和理解顺序栈的相关知识点。在下一篇内容中,我们会介绍如何通过C语言实现链栈,大家记得关注哦!

最后感谢各位的翻阅,咱们下一篇再见!!!

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

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

相关文章

OpenAIOps社区线上宣讲会圆满召开,期待您的加入!

2024年1月12日“OpenAIOps社区”线上宣讲会圆满召开,群体智慧协同创新社区的创立为AIOps领域未来发展注入了活力。OpenAIOps社区是一个AIOps开源社区及创新平台,由中国计算机学会(CCF)、清华大学、南开大学、中科院、国防科大、必示科技等单位共同发起&a…

Java环境变量——Windows和Linux配置jdk

本文我主要是介绍jdk的下载方式和在Windows系统下安装配置jdk11(压缩包格式),其他格式的jdk以及Linux操作系统上的jdk安装我后续视情况进行更新… JDK的下载 大家可以去官网Java|Oracle下载对应的资源 继续往下翻,就可以看到Jav…

中国数据库市场的领军黑马——亚信安慧AntDB数据库

自2008年问世以来,亚信科技AntDB数据库一直在中国国产数据库市场中崭露头角,尤其在信创政策的大力支持下,成为这一领域的一匹黑马。经过多次迭代,AntDB已经发展到了7.0版本,为超高强度和密度的业务需求提供了强有力的解…

微信服务号和订阅号区别

服务号和订阅号有什么区别?服务号转为订阅号有哪些作用?首先我们要知道服务号和订阅号有什么区别。服务号侧重于对用户进行服务,每月可推送4次,每次最多8篇文章,发送的消息直接显示在好友列表中。订阅号更侧重于信息传…

目标检测中的数据增强

整个代码参考:bubbliiiing/object-detection-augmentation。 random_data.py import cv2 import numpy as np from PIL import Image, ImageDrawdef rand(a=0, b=1):return np.random.rand()*(b-a) + adef get_random_data(annotation_line, input_shape, jitter=.3, hue=.1…

几个简单好用Python库,让你工作效率翻倍

概要 Python是一门强大的编程语言,不仅可以进行软件开发,还可以通过各种优秀的第三方库来提高工作效率。本文将介绍几个简单而好用的Python库,它们可以帮助你在各种领域提高工作效率,从数据处理到图形设计,再到网络爬…

提升设备巡检效率:易点易动设备管理系统的应用

设备巡检是企业保持设备正常运转和提高生产效率的重要环节。然而,传统的设备巡检过程通常繁琐而耗时,容易出现信息遗漏和延误。为了解决这一问题,现代企业可以利用智能化的设备管理系统来提升设备巡检的效率。易点易动设备管理系统是一种功能…

ED UV灯FCC认证的辐射与传导整改实例

摘要:某型LED UV灯出口美国,因此需要满足美国FCC标准要求。常规来说这个UV灯是需要测试FCC PART18标准要求的。但是,这个虽然是uv灯,但是利用的紫外线图层改变led的发光,而不是标准里面的定义的uv灯是放电灯&#xff0…

HTML中使用less

首先,什么是less? less和css 区别: 1、Less是一门CSS预处理语言,而css是一种用来表现HTML或XML等文件样式的计算机语言; 2、less扩展了CSS语言,增加了css本身没有的变量、函数等特性; 3、css可…

【C语言】ipoib驱动 - ipoib_cm_post_receive_srq_rss函数

一、ipoib_cm_post_receive_srq_rss函数定义 static int ipoib_cm_post_receive_srq_rss(struct net_device *dev,int index, int id) {struct ipoib_dev_priv *priv ipoib_priv(dev);struct ipoib_recv_ring *recv_ring priv->recv_ring index;struct ib_sge *sge;stru…

RK3568驱动指南|驱动基础进阶篇-进阶5 自定义实现insmod命令实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工…

Qt QProgressBar进度条控件

文章目录 1 属性和方法1.1 值1.2 方向1.3 外观1.4 信号和槽 2 实例2.1 布局2.2 代码实现 QProgressBar是进度条控件,进度条用来指示任务的完成情况 1 属性和方法 QProgressBar有很多属性,完整的可查看帮助文档。这里以QProgressBar为例,列出…

HackTheBox - Medium - Windows - Scrambled

Scrambled 最近身体有些不舒服,恐怕理论值要与现实产生较大偏差了 Scrambled 是一台中型 Windows Active Directory 计算机。通过枚举远程计算机上托管的网站,潜在攻击者能够推断出用户“ksimpson”的凭据。该网站还指出 NTLM 身份验证已禁用&#xff0…

友思特分享丨高精度彩色3D相机:开启崭新的彩色3D成像时代

来源:友思特 机器视觉与光电 友思特分享丨高精度彩色3D相机:开启崭新的彩色3D成像时代 原文链接:https://mp.weixin.qq.com/s/vPkfA5NizmiZmLiy_jv3Jg 欢迎关注虹科,为您提供最新资讯! 3D成像的新时代 近年来&#…

多合一小程序商城系统源码:支持全平台端口 附带完整的搭建教程

现如今,随着移动互联网的飞速发展,小程序已经成为电商行业的新宠。罗峰给大家分享一款多合一小程序商城系统源码。该系统旨在为商家提供一个功能强大、易于搭建和管理的电商平台,帮助商家快速占领市场,提高品牌影响力。 以下是部…

2、指令系统、存储系统和缓存

指令系统 计算机指令的组成 1、操作码——需要完成什么样的操作2、操作数——参与运算的数据以及单元地址以上两个都是由二进制编码存储 计算机指令执行过程 指令的寻址方式(怎么样找到操作数?) 指令组成 操作码字段地址码字段 1、顺序寻…

数据可视化大屏自适应,保持比例不变形,满足不同分辨率的需求——利用transform的scale属性缩放,缩放整个页面。

文章目录 一、需求背景:二、需求分析:三、选择方案:四、实现代码:五、效果预览:六、封装组件: 一、需求背景: 数据可视化大屏是一种将数据、信息和可视化效果集中展示在一块或多块大屏幕上的技…

为什么C#要采用顶级语句?

前言 有群友问:为什么C#要采用顶级语句? .NET6发布后,C#10莫名引入了顶级语句,这是一种简化代码结构的语言特性。在此之前,C#程序必须包含一个入口点,通常是Main方法,然后在该方法中编写主要的…

Node.js 后端框架--Cool

1. 一个项目用COOL就够了 开源免费、全面覆盖、AI编码快速开发v7.0 快速开始AI编码为什么选 Cool?在 GitHub 上查看 给大家推荐一个 后端框架 cool node.js js工作者 学习成本极低 后台管理系统 软件开发能不能快一点,CRUD开发者 加班中... 摸鱼中…

【C#】当重复使用一段代码倒计时时,定义接口类和通过实现类继承接口方式进行封装方法和体现代码灵活性

欢迎来到《小5讲堂》 大家好,我是全栈小5。 这是《C#》序列文章,每篇文章将以博主理解的角度展开讲解, 特别是针对知识点的概念进行叙说,大部分文章将会对这些概念进行实际例子验证,以此达到加深对知识点的理解和掌握。…