c语言-数据结构-栈和队列的实现和解析

       

目录

一、栈

1、栈的概念

1.2 栈的结构

 2、栈的创建及初始化

3、压栈操作

4、出栈操作

5、显示栈顶元素

6、显示栈空间内元素的总个数

7、释放栈空间

8、测试栈

二、队列

1、队列的概念

1.2 队列的结构

2、队列的创建及初始化

3、入队

4、出队

5、显示队头、队尾数据

5.1 显示队头数据

5.2 显示队尾数据

6、显示队列的数据个数

7、释放队列

8、测试队列

结语:


前言:

        栈和队列都是一种特殊的线性表,线性表是一种被广泛应用的数据结构。之所以叫线性表是因为其在逻辑上是连续的,可以将他想象成一条线把数据都连了起来,但在物理上并不一定是连续的。在用线性表存储数据的时候,通常以链式结构和数组形式来进行数据的存储。本文介绍的栈和队列就是使用数组和链表实现的。

一、栈

1、栈的概念

        栈的特点是只能从固定的一端进行数据的输入和输出,通常称为压栈和出栈。数据必须满足先入后出的状态,把数据的输入和输出端口称为栈顶,另一端称为栈底,随着不断的进行压栈操作,原先栈顶的元素就会被压到栈底。出栈时,栈底元素必须等上面栈顶元素都出栈了才能出栈。

1.2 栈的结构

        栈中的数据输入和输出始终遵循着先入后出的法则。

        接下来就使用代码来实现栈,分析栈的各个功能:压栈、出栈、显示栈顶元素、显示栈空间元素的个数、释放栈空间。 

 2、栈的创建及初始化

        这里用数组的方式来实现栈,因为数组的优势在于尾插尾删的时候,数组的效率还是很高的,当然如果用数组进行头插头删则每一次的插入和删除都需要将整个数组进行移动,效率就非常低了。但是栈的特点是只有一端进行输入输出数据即可,所以我们把数组的末尾当成栈的栈顶来看待,则用不到数组头插头删的功能。

        首先创建栈的结构体:

typedef int STDataType;//int类型重定义
typedef struct Stack
{STDataType* arr;//arr可以理解为数组首地址,后续用该指针开辟空间int top;//表示栈顶的元素,即数组的最后一个元素int capacity;//栈的容量
}ST

        栈的初始化:

void STInit(ST* pst)//初始化栈
{assert(pst);pst->arr = NULL;//初始化的时候先置空,后续插入数据时在开辟空间pst->top = 0;//最开始没有元素因此栈顶为0pst->capacity = 0;//初始化容量也可以设置为0
}

        这里有一个注意点:top的位置是栈顶元素的下一个位置,如下图所示:

3、压栈操作

        压栈就是将数据放到数组的末尾处,因为数组的末尾对应的是栈顶位置,压栈就是从栈顶放入元素。压栈完成后top++,代码如下:

void STPush(ST* pst, STDataType x)//压栈
{assert(pst);if (pst->capacity == pst->top)//创建一个数组,同时还能达到扩容的效果{int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;//三目操作STDataType* temp = (STDataType*)realloc(pst->arr,sizeof(STDataType*) * newcapacity);//在arr指针指向空的时候,realloc也具有malloc的效果if (temp == NULL)//检查开辟空间是否成功{perror("STPush");return;}pst->arr = temp;//若开辟成功,则将开辟好的空间给到arr指针pst->capacity = newcapacity;//更新数组大小}pst->arr[pst->top] = x;//压栈操作,将数组压进数组(栈)内pst->top++;//更新top至下一个位置
}

4、出栈操作

        出栈操作就很简单了,因为出栈意味着将该元素移出数组,而且栈遵循先入先出,数组最先进来的元素肯定是末尾的元素,因此直接将末尾的元素“删除”即可,但是我们知道数组不存在删除一个元素的说法,因此使用top--的方式,让top往前移一位,等到下次压栈新的元素会覆盖出栈的元素,达到出栈的效果。

        这里有一个细节:进行出栈操作的时候要注意此时栈不能为空,因此特意写一个对栈判空的函数,判空函数如下:

bool Empty(ST* pst)
{assert(pst);return pst->top == 0;//如果栈为空则返回真
}

        出栈代码如下:

void STPop(ST* pst)//出栈
{assert(pst);//若Empty函数为空返回真,这里对其结果取非就可以达到检查栈是否为空的效果assert(!Empty(pst));//断言使用函数Empty返回值进行判空pst->top--;//top--之后,下一次新的数据压栈会覆盖原来的数据
}

5、显示栈顶元素

        显示栈顶元素就很简单了,直接返回数组末尾的元素即可,因为数组末尾等于栈顶。这里也要注意若调用此函数则栈不能为空,因为栈为空了也就不存在栈顶元素。

        显示栈顶元素代码如下:

STDataType STtop(ST* pst)//显示栈顶元素
{assert(pst);assert(!Empty(pst));//判空return pst->arr[pst->top - 1];//数组末尾即top的位置减1
}

6、显示栈空间内元素的总个数

        之所以让top指向栈顶元素的后一位的好处:此时top的值就是栈空间的元素总和,因此直接返返回top的值即可。

        栈元素总和代码如下:

STDataType STsize(ST* pst)//显示栈的所有元素的个数
{assert(pst);return pst->top;
}

        这里跟前面的情况不一样了,栈为空表示栈里一个元素没有,即为0,因此无需对栈进行判空。

7、释放栈空间

        栈空间是在堆上申请而来的,因此使用完之后应该手动释放。

        释放栈空间代码如下:

void STDestroy(ST* pst)//释放空间
{assert(pst);free(pst->arr);//释放arr,也就是栈的空间pst->capacity = 0;pst->top = 0;
}

8、测试栈

        在准备了栈的各个功能,接下来就对这些功能进行测试。

        测试代码如下:

#include"stack.h"test1()
{ST st;//创建一个结构体变量st,其实操作栈就是通过操作该结构体st中的指针arr实现的STInit(&st);//初始化,传st的地址,这样就能够通过其地址对st里的成员进行操作STPush(&st, 1);//压栈STPush(&st, 2);STPush(&st, 3);STPush(&st, 4);printf("size:%d\n", STsize(&st));//打印栈空间内元素个数while (!Empty(&st)){printf("%d ", STtop(&st));//打印栈顶元素STPop(&st);//出栈}STDestroy(&st);//释放栈
}int main()
{test1();return 0;
}

        运行结果:

        从运行结果来看,出栈的顺序是4 3 2 1,而我们入栈的顺序是1 2 3 4,顺序刚好与栈的特点先入后出对应上。

二、队列

1、队列的概念

        栈是只有一个端口可以进行输入输出数据,而队列是有两个端口可以操作,其中一个端口进行的是输入数据,也叫入队。另一个端口进行输出数据(删除数据),叫出队。其中队列满足先进先出的准则,即数据从队尾进入,然后从队头输出。

1.2 队列的结构

        举例说明:如果入队的顺序是1 2 3 4,那么出队的顺序也是1 2 3 4,与栈的特点相反。 

        接下来就使用代码来实现队列,分析队列的各个功能:入队、出队、显示队头数据、显示队尾数据、显示队列大小等功能。 

2、队列的创建及初始化

        首先因为队列涉及到两个端口的操作,因此用数组实现队列会遇到这样一个问题:即进行数组头部数据的更改。我们都知道如果操作数组头部数据的代价是很大的,因为要移动整个数组的元素,效率非常之低。

        所以我们选择用单链表的结构实现队列,只不过此单链表被两个指针维护,一个是头指针,一个是尾指针,刚好对应队列的两个端口。头指针实现出队操作,尾指针实现入队操作

        队列的创建代码如下:

typedef int QueueDataType;//int类型重定义
typedef struct QNode//节点的结构体
{struct QNode* next;//节点里的指针QueueDataType data;//数据
}QNode;typedef struct Queue//队列的结构体
{struct QNode* head;//队列头指针struct QNode* tail;//队列尾指针int size;//队列数据的个数
}Queue;

        为什么会有两个结构体呢?第一个结构体是组成链表的节点的结构体。第二个结构体是因为要记录队列的头尾指针,和队列的大小,因此这三个变量放在一个结构体中很省事,调用起来也很方便,因此又创建了一个结构体。

        队列的初始化代码:

void QueueInit(Queue* pq)//初始化
{assert(pq);pq->head = NULL;//最开始一个节点都没有,因此头指针置空pq->tail = NULL;//尾指针也是如此pq->size = 0;//最开始没有任何节点,因此队列数据个数为0
}

3、入队

        把链表的尾部看成队尾,因此入队操作从队尾开始进行,即尾插的概念。

        入队代码如下:

QNode* BuyNode(QueueDataType x)//创建节点函数
{QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("BuyNode");return NULL;}newnode->next = NULL;newnode->data = x;return newnode;
}void QueuePush(Queue* pq, QueueDataType x)//入队
{assert(pq);QNode* newnode = BuyNode(x);//创建一个节点//判断两种情况if (pq->head == NULL)//1是链表为空时,尾插入的第一个数据也要改变head指针的指向{assert(pq->tail==NULL);pq->head = pq->tail = newnode;//head和tail都指向newnode节点}else//2是链表中存在节点,则直接改变tail指针的指向即可{pq->tail->next = newnode;pq->tail = newnode;}pq->size++;//入队成功后,队列数据个数+1
}

4、出队

        既然入队是在链表尾部实现的,那么出队就在链表的另一端口实现,即链表的头指针负责实现出队,用到的是头删的概念。既然是头删的概念,则需要注意链表为空的情况,因此需要对链表进行判空,判空的逻辑与上文栈判空逻辑一样。

        出队代码如下:

bool Empty(Queue* pq)//判空函数
{assert(pq);return pq->head == NULL|| pq->tail==NULL;//若头、尾指针都为空则返回真
}void QueuePop(Queue* pq)//出队
{assert(pq);assert(!Empty(pq));//函数Empty返回值为真,则对其取非触发断言效果,则会报错//两种情况if (pq->head == pq->tail)//1是当链表只有一个节点的情况,需要将tail也置空{free(pq->head);pq->head = pq->tail = NULL;}else//2是多个节点的情况,无需将tail置空,只需要将head指针更新{QNode* poi = pq->head;pq->head = pq->head->next;free(poi);}pq->size--;//删除完毕后链表中数据的个数-1}

5、显示队头、队尾数据

5.1 显示队头数据

        队头就是头指针指向的节点,即返回头指针指向节点的数值即可。

        代码如下:

QueueDataType QueueFront(Queue* pq)//显示队头数据
{assert(pq);assert(!Empty(pq));//链表判空,链表为空不能显示队头数据return pq->head->data;//返回头指针指向节点的数值
}

5.2 显示队尾数据

        队尾就是尾指针指向的节点,因此直接返回尾指针指向的节点的数值即可。

        代码如下:

QueueDataType QueueBack(Queue* pq)//显示队尾数据
{assert(pq);assert(!Empty(pq));//链表判空,链表为空不能显示队尾数据return pq->tail->data;//返回尾节点的数值
}

6、显示队列的数据个数

        这里就体现出size的作用,直接返回size即可。

        代码如下:

int QueueSize(Queue* pq)//显示队列大小
{assert(pq);return pq->size;
}

7、释放队列

        因为队列是由malloc在堆上申请的空间,因此使用完之后要手动释放。

        释放代码如下:

void QueueDestroy(Queue* pq)//释放队列
{assert(pq);QNode* cur = pq->head;//cur指针代替head去遍历链表while (cur){QNode* poi = cur->next;//poi标记下一个节点的位置free(cur);//释放cur指针指向的节点cur = poi;//更新cur指针}pq->head = pq->tail = NULL;//最后head和tail都需要手动置空pq->size = 0;//size重新置为0
}

8、测试队列

        测试一下写出来队列的各个功能,测试代码如下:

#include"Queue.h"int main()
{Queue p;//创建结构体pQueueInit(&p);//结构体p的初始化QueuePush(&p, 1);//入队QueuePush(&p, 2);QueuePush(&p, 3);printf("%d ", QueueFront(&p));//查看当前队头数据QueuePop(&p);//出队QueuePush(&p, 4);printf("size:%d\n", QueueSize(&p));//查看当前队列里数据的个数while (!Empty(&p))//循环打印队头的数据{printf("%d ", QueueFront(&p));QueuePop(&p);}QueueDestroy(&p);//释放队列return 0;
}

        运行结果:

        入队顺序是1 2 3 4。从结果来看,即使中途出队一个数据,整体出队的顺序还是没有改变, 出队顺序依然是1 2 3 4,符合队列的特点:先入先出。

结语:

        以上就是关于栈和队列的实现与解析,希望本文可以带给你更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!(❁´◡`❁)

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

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

相关文章

番外 1 : Java 环境下的 selenium 搭建

Java 环境下的 selenium 搭建 一 . 下载谷歌浏览器二 . 下载谷歌浏览器驱动2.1 查看谷歌浏览器版本2.2 下载对应版本的谷歌驱动2.3 解压下载好的驱动压缩包 , 将下载好的 chromedriver.exe 放到java 系统环境变量下 三 . 下载 Edge 浏览器的驱动3.1 查看 Edge 浏览器的版本3.2 …

带有密码的Excel只读模式,如何取消?

Excel文件打开之后发现是只读模式,想要退出只读模式,但是只读模式是带有密码的,该如何取消带有密码的excel只读文件呢? 带有密码的只读模式,是设置了excel文件的修改权限,取消修改权限,我们需要…

Go,14周年[译]

国内的双十一购物狂欢已没有了当年的那种热闹与喧嚣,但大洋彼岸的Go团队却始终保持稳中有增的开发和语言演进节奏。今晨Go核心团队的Russ Cox[1]代表Go语言项目团队在Go官博上发表了《Fourteen Years of Go》[2]的博文,纪念Go语言开源14周年[3]&#xff…

FCOS难点记录

FCOS 中有计算 特征图(Feature map中的每个特征点到gt_box的左、上、右、下的距离) 1、特征点到gt_box框的 左、上、右、下距离计算 x coords[:, 0] # h*w,2 即 第一列y coords[:, 1] l_off x[None, :, None] - gt_boxes[..., 0][:, No…

XXE XML外部实体注入

XXE 外部实体注入 一, 简介 XXE(XML External Entity Injection)是一种 XML 注入攻击,它利用了 XML 解析器在处理 XML 文档时存在的漏洞。 攻击者通过在 XML 文档中插入外部实体的引用,可以引导 XML 解析器读取攻击者控制的外部…

编程艺术之源:深入了解设计模式和设计原则

深入了解设计模式和设计原则 一、认识设计模式1.1、设计模式是什么?1.2、设计模式是怎么来的?1.3、设计模式解决了什么问题? 二、设计模式的基础2.1、面向对象思想2.2、设计原则 三、如何学习设计模式3.1、明确目的3.2、学习步骤 总结 一、认…

Linux服务器从零开始训练 RT-DETR 改进项目 (Ultralytics) 教程,改进RTDETR算法(包括使用训练、验证、推理教程)

手把手从零开始训练 RT-DETR 改进项目 (Ultralytics版本) 教程,改进RTDETR算法 本文以Linux服务器为例:从零开始使用Linux训练 RT-DETR 算法项目 《芒果剑指 RT-DETR 目标检测算法 改进》 适用于芒果专栏改进RT-DETR算法 文章目录 百度 RT-DETR 算法介绍改进网络代码汇总第…

CAD Exchanger SDK 有什么新内容?

CAD 交换器 3.23.0,2023 年 11 月强调:- 添加了新版本格式的导入:Autodesk Inventor 2023 和 2024、NX 2306。- 文档经过重大修改,使其更易于导航。它也是现在包含有关 SDK、Web Toolkit 和 Manufacturing Toolkit 的全面信息&…

SQL 存储过程优化

问题:一个复杂的6个表的left jion 语句,发现设置为定时任务后最高时长为18分钟 1、原因分析:对复杂SQL 进行拆分验证 发现是合同明细表和 产品表的left jion 时间过长,发现 合同明细表每天为3w条,之前做过优化 对每个…

PTA 7-6 队列模拟

7-6 队列模拟 分数 25 全屏浏览题目 作者 sy 单位 宁波财经学院 设从键盘输入一整数序列a1&#xff0c;a2&#xff0c;...an&#xff0c;试编程实现&#xff1a;当ai>0 时&#xff0c;ai 进队&#xff0c;当ai<0 时&#xff0c;将队首元素出队&#xff0c;当ai0 时&a…

10. 深度学习——模型优化

机器学习面试题汇总与解析——模型优化 本章讲解知识点 前言低秩近似剪枝与稀疏约束参数量化二值网络知识蒸馏紧凑的网络结构本专栏适合于Python已经入门的学生或人士,有一定的编程基础。本专栏适合于算法工程师、机器学习、图像处理求职的学生或人士。本专栏针对面试题答案进…

(1)(1.14) LightWare SF10/SF11激光雷达

文章目录 前言 1 串行连接 2 I2C 连接 3 参数说明 前言 Lightware SF20 和 LW20 是体积小、测距远&#xff08;100m&#xff09;、精度高的测距仪。有两种型号&#xff0c;LW20/Ser 使用串行接口&#xff0c;LW20/I2C 使用 I2C 接口。 1 串行连接 对于串行连接&#xff0…

java数据结构--堆

目录 一.概念 二.堆中最重要三个方法 三.大顶堆 四.基于数组实现大顶堆 五.堆排序 六.小顶堆 七.基于数组实现小顶堆 八.ProiorityQueue和Heap 示例&#xff1a; 九.求数组中第K大元素 十.求数据流中第K大元素 十一.求数据流的中位数 一.概念 堆&#xff08;Heap&…

软件开发项目文档系列之十六如何撰写系统运维方案

前言 项目运维方案是为了确保项目的稳定运行和可持续发展而制定的指导性文档。本文将详细介绍项目运维方案的各个方面&#xff0c;包括硬件和软件基础设施、监控和警报、备份和恢复、安全性、团队组织和沟通等方面。本博客将提供示例和最佳实践&#xff0c;以帮助您更好地理解…

什么是ajax,ajax有什么特点?

AJAX&#xff08;Asynchronous JavaScript and XML&#xff09;是一种用于在后台与服务器进行异步通信的技术。它使用 JavaScript、XML&#xff08;或 JSON&#xff09;和 XMLHttpRequest 对象来实现在不刷新整个页面的情况下更新部分页面内容。 特点&#xff1a; 异步通信&a…

Ubuntu显示毫秒级时间

Ubuntu显示毫秒级时间 1. 打印当前时间 1. 打印当前时间 date 时间&#xff0c;转化成毫秒级 $ date # Mon 03 Apr 2023 11:09:47 PM CST$ echo -e "$(date %T).$((10#$(date %N)/1000000))" # 23:09:55.552谢谢

一些分享| 在线笔记、GIF图片生成方法

文章目录 在线笔记视频转GIF 本片博客旨在挖掘一些好用且免费的在线平台&#xff0c;持续更新中~ 正所谓科技解放双手&#xff0c;使用在线平台可以方便快捷地学习办公&#xff0c;节省时间。 在线笔记 语雀 https://www.yuque.com/dashboard 语雀是笔者用得最长最久的平台了…

maven: Cannot access nexus-all xxx in offline mode错误的解决

1. 絮絮叨叨 之前&#xff0c;同事告诉过一个加速编译的mvn命令 mvn -o -T 4C clean install -DskipTests -Dmaven.compile.forktrue 忽然&#xff0c;上周在公司的蛇口编译机器&#xff0c;执行这个命令就行不通了 错误信息&#xff0c;由于编以输出太多被淹没了&#xff0c…

Leetcode—102.二叉树的层序遍历【中等】

2023每日刷题&#xff08;二十四&#xff09; Leetcode—102.二叉树的层序遍历 C语言BFS实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Return an array of arr…

线性表——编写一个函数,将数组中两个顺序表的位置互换,即将(b1,b2,b3,...,bn)放在(a1,a2,a3,...,an)的前面。

题目&#xff1a;已知在一维数组A[mn]中依次存放两个线性表&#xff08;a1,a2,a3,...,an&#xff09;和&#xff08;b1,b2,b3,...,bn&#xff09;。编写一个函数&#xff0c;将数组中两个顺序表的位置互换&#xff0c;即将&#xff08;b1,b2,b3,...,bn&#xff09;放在&#xf…