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

顺序栈的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语言实现链栈,大家记得关注哦!

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数学建模美赛M奖速成系列】数据可视化(二)

数据可视化(二) 写在前面百分比堆叠线条图优点缺点实现pythonmatlab 火山图优点实现pythonmatlab 最后 写在前面 上一篇文章为大家分享了山脊图和气泡图的绘图方法与代码,这里学姐为继续为大家分享百分比堆叠线条图和火山图,包含…

Linux下的HTTPS配置:从证书到安全连接

在当今的互联网环境中,数据传输的安全性越来越受到重视。HTTPS,作为HTTP的安全版本,通过使用SSL/TLS协议来加密数据传输,确保了数据在传输过程中的安全。在Linux环境下,配置HTTPS需要从证书的生成到服务器的配置进行一…

RT-Thread入门笔记3-线程的创建

线程 RT-Thread 中,线程由三部分组成:线程代码(入口函数)、线程控制块、线程堆栈. 线程代码: 线程控制块 : 线程控制块是操作系统用于管理线程的一个数据结构, 它会存放线程的一些信息, 例如优先级、 线程…

[Python练习]使用Python爬虫爬取豆瓣top250的电影的页面源码

1.安装requests第三方库 在终端中输入以下代码(直接在cmd命令提示符中,不需要打开Python) pip install requests -i https://pypi.douban.com/simple/ 从豆瓣网提供的镜像网站下载requests第三方库 pip install requests 是从国外网站下…

为何劳保鞋现在如此受欢迎,这就是原因!

当代年轻人最大的消费原则,必须是不花半点冤枉钱,伴随着军大衣成为“时尚单品”,硬核劳保鞋也大受欢迎。今天百华小编就与大家一起看看劳保安全鞋为何如此受大众欢迎呢。 首先,随着人们安全意识的提高,对个人安全和健康…

手把手教你学会接口自动化系列十一-将用例写在json中,持久化管理起来下

上一篇我写了登录,我们发现json还是没有什么大问题,还蛮好用的,但是我们再写下一个,比如线索新建接口的时候,我们写着写着会发现问题: 我们写获取url的没有问题,代码如下: # !/usr…

vue:使用【3.0】:拖拽数据

1、参考链接&#xff1a;vue.draggable中文文档 - itxst.com 2、想要实现的效果图&#xff1a;红框内容可以拖拽 3、安装 yarn add vuedraggablenext npm i -S vuedraggablenext 4、代码 <template><draggable:list"columns"ghost-class"ghost&qu…

mac下配置git自定义快捷命令

1. 指定自定义别名 vi ~/.bash_profile open ~/.bash_profile 配置环境变量,插入类似下面的内容 alias gcgit checkout alias gmgit commit -m alias gcbgit checkout -balias gtgit statusalias gagit add .alias glggit logalias gdgit diffalias grnmgit rm node_modul…

[python]pyside6安装和在pycharm配置

安装命令&#xff1a; pip install PySide6 -i https://mirror.baidu.com/pypi/simple Pycharm配置Pyside6 打开Pycharm点击File -> Settings -> Tools -> External Tools&#xff0c;点击&#xff0b;。需要添加 Pyside6-Designer 、 Pyside6-UIC 和 Pyside6-rcc三…

新手入门Java 方法带参,方法重载及面向对象和面向过程的区别介绍

第二章 方法带参 课前回顾 1.描述类和对象的关系 类是一组对象的共有特征和行为的描述。对象是类的其中一个具体的成员。 2.如何创建对象 类名 对象名 new 类名();3.如何定义和调用方法 public void 方法名(){}对象名.方法名();4.成员变量和局部变量的区别 成员变量有初…

【MySQL高级】——索引的创建设计原则

1. 索引的声明&使用 <1> 索引分类 功能逻辑 说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。物理实现方式 索引可以分为 2 种&#xff1a;聚簇索引和非聚簇索引。作用字段个数 索引可以分为 2 种&#xff1a;单列索引和…

无迹卡尔曼滤波(Unscented Kalman Filter, UKF):理论和应用

无迹卡尔曼滤波&#xff08;Unscented Kalman Filter, UKF&#xff09;&#xff1a;理论和应用 卡尔曼滤波是一种强大的状态估计方法&#xff0c;广泛应用于控制系统、导航、机器人等领域。然而&#xff0c;传统的卡尔曼滤波假设系统是线性的&#xff0c;而在实际应用中&#…

一篇文章带你了解接口测试(总结)

接口测试是软件测试中的一块重要部分&#xff0c;简言之&#xff0c;接口测试是指验证软件系统中各个模块间接口处的交互是否正确。 接口是软件组件之间交互的协议&#xff0c;允许不同的软件系统或模块通过明确定义的方法通信和交换数据。 一. 接口测试的重要性 在微服务架…

USB-C一线通桌面显示器你有见过么?

新型的TYPE-C接口桌面显示器&#xff0c;宛如一位多才多艺的艺术家&#xff0c;它不仅精于视频传输&#xff0c;更在充电领域展现出无与伦比的才华。不同于传统的显示器&#xff0c;它化平凡为神奇&#xff0c;将显示器的DC电源巧妙地转换成PD协议&#xff0c;为各种设备提供稳…

德思特干货丨如何使用SBench6软件对数字化仪采集信号进行处理?(二)——平均运算功能

来源&#xff1a;德思特测量测试 德思特干货丨如何使用SBench6软件对数字化仪采集信号进行处理&#xff1f;&#xff08;二&#xff09;——平均运算功能 原文链接&#xff1a;https://mp.weixin.qq.com/s/j-iN_2Jrn9ZHGMaaAYsDJg 欢迎关注虹科&#xff0c;为您提供最新资讯&…

从静态到动态:视频美颜SDK在短视频平台的应用全面解析

为了在短视频平台上实现更出色的美颜效果&#xff0c;开发者们通常会借助视频美颜SDK。本文将深入探讨视频美颜SDK在短视频平台上的应用&#xff0c;从技术原理、性能优化到用户体验等方面进行全面解析。 一、技术原理与算法演进 视频美颜SDK的核心在于其算法&#xff0c;而…

AIGC ChatGPT 4 Prompt 万能提示词公式

最近大家都在使用ChatGPT来帮助自己完成相应的工作。很多时候大家提出的问题得不到很清晰,很明确的答案。 我们应该怎么样来和ChatGPT进行有效的沟通呢? 例如我们先来问一问ChatGPT: 要获得最准确的回复,请确保遵循以下建议: 明确性:请尽量明确描述您的问题。确保提供足…

【playwright】新一代自动化测试神器playwright+python系列课程00——playwright安装

playwright安装 本文主要分享由微软开发的实现Web UI自动化测试工具Playwright库&#xff0c;相比于之前学习过selenium库&#xff0c;playwright对于编写自动化代码绝对是更轻松了&#xff0c;因为它支持脚本录制&#xff0c;如果只求简单点可以不用写一行代码就能够实现自动…

用bat脚本执行py文件以及批量执行py文件(全网超详细)

1.前言 对于python代码&#xff0c;每次执行一个文件就要运行一个命令&#xff0c;太过麻烦 在Windows电脑上&#xff0c;想一次性执行多个python文件的代码&#xff0c;就需要用到bat脚本 2.python代码 先写几个python代码的文件 如下图 3.py文件为中文&#xff0c;用bat执…

Linux学习记录——삼십칠 传输层TCP协议(2)

文章目录 1、滑动窗口2、拥塞控制3、延迟应答4、捎带应答5、总结TCP可靠性和性能提高6、面向字节流7、粘包问题8、异常情况9、全连接、半连接 上一篇是传输层TCP协议&#xff08;1&#xff09;。本篇默认读者已经清楚TCP报头各个部分、可靠性和握手挥手的含义。 有时候会把客户…