C语言基础数据结构——栈和队列

目录

1.栈

1.1栈的选型

1.2 实现代码

2.队列

2.1整体思路

2.2初始化和销毁

2.3出入队列

2.4取队列元素

2.5判断队列是否为空

2.6返回队列中元素个数 

2.7 Test 


1.栈

       栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO Last In First Out )的原则。 就像给枪械弹夹装子弹一样
                             
                                                      (图片源于网络)
压栈:栈的插入操作叫做进栈 / 压栈 / 入栈, 入数据在栈顶 。(压入子弹)
出栈:栈的删除操作叫做出栈。 出数据也在栈顶 。(射出子弹)
Tips:

          数据结构中的栈和堆只是与内存中的栈和堆名字相同,

实际上并没有太多联系。

                                                                         

1.1栈的选型
栈的实现一般可以使用 数组或者链表实现 ,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的 代价比较小。
只有一头去变化,数组使用更快。

1.2 实现代码

初始化如下

typedef int STDataType;
typedef struct Stack {
	STDataType* arr;
	int capacity;
	int top;
}ST;
top作为栈顶
对top理解如下:
入栈的顺序就是1 2 3 4。

stackpush是唯一的放入数据的操作

top等于零时每插入一个元素就top++,指向栈顶元素的下一个

top指向栈顶元素的话必须让top=-1,如果还让top=0,那top=0时就不能区分此时是否有元素。

其余接口的实现(几乎等同于顺序表):

void STInit(ST* pst) {
	assert(pst);
	pst->top = 0;//初始状态可以是0也可以是-1
	pst->capacity = 0;
	pst->arr = NULL;
}

void STPush(ST* pst,STDataType x) {
	assert(pst);
	if (pst->top == pst->capacity) {
		int newcapacity = pst->capacity == 0 ? 4 : 2 * pst->capacity;
		STDataType* tmp = (STDataType*)realloc(pst->arr, sizeof(STDataType) * newcapacity);
		if (!tmp) {
			perror("realloc failed!");
			exit(1);
		}
		pst->arr = tmp;
		pst->capacity = newcapacity;
	}
	pst->arr[pst->top++] = x;
}

void STPop(ST* pst) {
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}

STDataType STTop(ST* pst) {
	assert(pst);
	assert(!STEmpty(pst));
	return pst->arr[pst->top - 1];
}

//删除或者取栈顶数据时都需要判断栈是否为空
bool STEmpty(ST* pst) {
	assert(pst);
	return pst->top == 0;
}

int STSize(ST* pst) {
	assert(pst);
	return pst->top;
}

void STDestroy(ST* pst) {
	assert(pst);
	free(pst->arr);
	pst->arr = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

 应用时,注意函数声明的顺序

栈的出入顺序不一定是固定的

答案是C

链式栈(单链表)建议将头作为栈顶,因为栈尾不方便进行删除操作

2.队列

  队列:

     只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出
FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾 出队列:进行删除操作的一端称为 队头。

    恰好与栈不同。

2.1整体思路

     用链表实现(单链表就足够了)更为合适,因为当数据的出和入不在同一边,若使用数组,势必有一段需要O(n)的复杂度来整体移动数组。

                  

       对于涉及链表尾部的操作,为了不遍历链表,我们加入一个ptail变量。

       因为涉及改变phead(队头)和ptail(队尾)的值,所以我们首先考虑使用phead和ptail的二级指针来维护头尾指针

struct Queue{
    QNode** pphead;
    QNode** pptail; 
};

单纯的链表不需要结构体来包含整个链表,但是队列需要结构体。我们可以类比的来看,顺序表中间有很多个值需要配合使用,所以不仅每个元素是结构体,整个顺序表也是个结构体。队列需要同时有头指针和尾指针(实现尾差和头删的功能),单独放两个有关队列的指针不合适,所以将两个指针放在一起构成一个新的结构体

(链表相对于队列,类似数组相对于顺序表)

    同时,将phead和ptail放进一个结构体时,上文所讲的二级指针问题也不存在了,可以直接通过结构体指针去更改phead和ptail

左为队列,右为链表 

(目前为止的队列和单链表的对比,本质依然是新瓶装旧酒)

 以上的设计在函数QueueSize(求队列中元素个数)中复杂度依然是O(n),但是如果在初始化和定义队列时加上size这个变量(加数据++,减数据--),整体复杂度又变回了O(1)。

        因此,数据结构的设计是灵活多变的,只是有一些传统,在传统的结构上我们应该根据自己的需要来增加、修改(包括是否需要size,是否需要哨兵位,是否需要头尾两个指针)

typedef int QDataType;
typedef struct QueueNode {
	QDataType val;
	struct QueueNode* next;
}QNode;

//为了便于维护,我们将链表进行设计而得到队列
typedef struct Queue {
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

再按照之前链表的实现方式,实现队列的各个接口。

2.2初始化和销毁

有了整体思路,目前学习的数据结构都应该先实现初始化和销毁接口。

void QueueInit(Queue* pq) {
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

void QueueDestroy(Queue* pq) {
	assert(pq);
	QNode* cur = pq->phead;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
2.3出入队列

只有“入队列”一个函数需要malloc,所以我们不再封装BuyNewNode函数:

void QueuePush(Queue* pq, QDataType x) {
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL) {
		perror("malloc fail!");
	}

	newnode->val = x;
	newnode->next = NULL;

	if (pq->phead == NULL) {
		pq->phead = pq->ptail = newnode;
	}
	else {
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
	pq->size++;
}

  每次进入队列,ptail就向后走,所以出队列的时候就应该出phead位置的Node 

但是以上代码有问题如下:

1.size数值没有变化

2.红笔圈出的部分可能是 NULL ptr

3.如果只有一个节点的话,ptail即将变成野指针

因此,对于出队列,需要分成:多个节点,一个节点,零节点三种情况进行讨论

void QueuePop(Queue* pq) {
	assert(pq);
	//0个节点,使用此类暴力检查或者带if语句的温柔检查
	assert(pq->phead && pq->ptail);
	//一个节点,单独
	if (pq->phead->next == NULL) {
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else {
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}
2.4取队列元素

虽然从理论上讲,队列只会从phead处取出数据,但是在具体运用中(如银行人工窗口的取号系统),我们要能够从头或者尾取出数据。

                                                  

实现如下: 

QDataType QueueFront(Queue* pq) {
	assert(pq);
	//必须使用暴力检查,因为有返回值
	assert(pq->phead);

	return pq->phead->val;
}

QDataType QueueBack(Queue* pq) {
	assert(pq);
	//必须使用暴力检查,因为有返回值
	assert(pq->ptail);

	return pq->ptail->val;
}

不同于没有返回值的出入队列函数,这两个函数都有返回值,若不适用暴力检查   来报错,就会返回一个不正确的值,埋下bug的种子。

Tips:

有读者问为什么后面的这些函数没使用perror检查是否成功,原因如下:

1.perror适用于偏系统的调用失败(如 malloc realloc)

2.这些调用如果真的失败,那电脑很可能已经出了大问题(那么小的空间都开辟不出来!!)

                                                    

2.5判断队列是否为空
bool QueueEmpty(Queue* pq) {
	assert(pq);
	return pq->size == 0;
}

很巧妙的写法,如果siez为0就返回1,表示确实为空。

2.6返回队列中元素个数 
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

体现了size变量的好处。

2.7 Test 

不管是队列还是栈,检验方法都不同于之前的链表和顺序表,需要一个print函数来打印,队列和栈都是边使用边出,遍历一遍之后数据就全部出去了。

先检查入队列和取头功能

 最正确的测试方法:

数据用一个出一个。 

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

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

相关文章

Docker入门二(应用部署、迁移与备份、DockerFile、docker私有仓库、Docker-Compose)

文章目录 一、应用部署1.MySQL部署2.Redis部署3.Nginx部署 二、迁移与备份1.容器做成镜像2.镜像备份和恢复(打包成压缩包) 三、DockerFile0.镜像从哪里来?1.什么是DockerFile2.DockerFile 构建特征3.DockerFile命令描述4.构建一个带vim的centos镜像案例5…

Oracle Primavera Analytics 是什么,与P6的关系?

前言 Oracle Primavera P6 Analytics 是与P6有关的一个相对较新的模块,Primavera 用户社区在很大程度上尚未对其进行探索。 那么它到底有什么作用呢? 通过了解得知它旨在通过深入了解组织的项目组合绩效,帮助高级管理层对其项目组合做出更好…

【开源】SpringBoot框架开发就医保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

MySQL | 表的约束

目录 1. 空属性 NULL 2. 默认值 DEFAULT 3. 列描述comment 4. zerofill 5. 主键 PRIMARY KEY 6. 自增长AUTO_INCREMENT 7. 唯一键UNIQUE 8. 外键 真正约束字段的是数据类型,但是数据类型约束很单一,需要有一些额外的约束,更好的保证数…

VS2019加QT5.14中Please assign a Qt installation in ‘Qt Project Settings‘.问题的解决

第一篇: 原文链接:https://blog.csdn.net/aoxuestudy/article/details/124312629 error:There’ no Qt version assigned to project mdi.vcxproj for configuration release/x64.Please assign a Qt installation in “Qt Project Settings”. 一、分…

AG32 MCU以太网应用实例demo

一. 前言 AGM32系列32位微控制器旨在为MCU用户提供新的自由度和丰富的兼容外设,以及兼容的引脚和功能。AG32F407系列产品具有卓越的品质,稳定性和卓越的价格价值。 AG32产品线支持其所有接口外设尽可能接近主流兼容性,并提供丰富的参考设计…

机器人路径规划:基于深度优先搜索(Depth-First-Search,DFS)算法的机器人路径规划(提供Python代码)

一、深度优先搜索算法介绍 深度优先搜索算法(Depth-First-Search)的基本思想是沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已…

代码学习记录21--回溯算法第二天

随想录日记part21 t i m e : time: time: 2024.03.16 主要内容:今天主要是结合类型的题目加深对回溯算法的理解:1:组合总和;2:电话号码的字母组合 216.组合总和III17.电话号码的字母…

维基百科推广秘诀13个方法助你成为行业领导者-华媒舍

维基百科(Wikipedia)作为全球最大、最权威的在线百科全书,拥有海量的知识内容,被广大用户广泛使用。对于任何一个领域的从业者来说,建立自己的维基百科页面,无疑是提升行业影响力的重要手段。本文将向您介绍…

LEETCODE 100255. 成为 K 特殊字符串需要删除的最少字符数

整体思路: 1.可以看到这道题是要求是最小的,那么可以想到遍历所有情况 2.把题干已知条件转换为一个数组,那么只需要以数组每个元素为开头遍历所有情况即可。 3.对于一个数考虑其后面的情况,其后每个数等于这个数k和数本身的最小值(遍历累计求…

【C语言】指针基础知识(一)

计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中。 一,内存和地址 内存被分为一个个单元,一个内存单元的大小是一个字节。 内存单元的编号(可以理解为门…

Ypay源支付2.8.8免授权聚合免签系统

本帖最后由 renleixiaoxu 于 2024-3-15 09:46 编辑 产品介绍 XPay是专为个人站长打造的聚合免签系统,拥有卓越的性能和丰富的功能。采用全新轻量化的界面UI,让您可以更加方便快捷地解决 知识付费和运营赞助的难题。同时,它基于高性能的Thin…

ubuntu安装docker的详细教程

检查卸载老版本docker ubuntu下自带了docker的库,不需要添加新的源。 但是ubuntu自带的docker版本太低,需要先卸载旧的再安装新的。 注:docker的旧版本不一定被称为docker,docker.io 或 docker-engine也有可能,所以卸…

Hypermesh碰撞安全之头部撞击模拟

1、首先到自定义工作面板中选择Engineering Solutions(工程解决方案) 2、进入行人保护建模流程模块 3、导入所需要的模型 4、对模型进行切割,选择所需要保留的区域 5、单击next进入下一界面 6、选择打击类型 下一步进入: 这样就完成了打击点…

基于深度学习的唇语识别系统的设计与实现

概要 人工智能作为三大工程之一,从上个世纪至今仍然活跃于各个行业的研究与应用之中,应时代的热潮方向,本 课题主要针对深度学习技术应用于唇语识别当中,实现词语唇语的翻译功能。唇语识别在图像处理中一直是一个富 有挑战性的课题…

基础知识学习 -- qnx 系统

QNX是一个基于优先级抢占的系统。 这也导致其基本调度算法相对比较简单。因为不需要像别的通用操作系统考虑一些复杂的“公平性”,只需要保证“优先级最高的线程最优先得到 CPU”就可以了。 基本调度算法 调度算法,是基于优先级的。QNX的线程优先级&a…

【LabVIEW FPGA入门】单周期定时循环

单周期定时循环详解 单周期定时环路是FPGA编程中最强大的结构之一。单周期定时循环中的代码更加优化,在FPGA上占用更少的空间,并且比标准While循环中的相同代码执行得更快。单周期定时环路将使能链从环路中移除,以节省FPGA上的空间。…

C++算法学习心得八.动态规划算法(4)

1.零钱兑换(322题) 题目描述: 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 你可以认为每种硬币的数量是无限的。…

QTextToSpeech的使用——Qt

前言 之前随便看了几眼QTextToSpeech的帮助就封装使用了,达到了效果就没再管了,最近需要在上面加功能(变换语速),就写了个小Demo后,发现不对劲了。 出现的问题 场景 写了个队列添加到语音播放子线程中&a…

多线程(代码案例: 单例模式, 阻塞队列, 生产者消费者模型,定时器)

设计模式是什么 类似于棋谱一样的东西 计算机圈子里的大佬为了能让小菜鸡的代码不要写的太差 针对一些典型的场景, 给出了一些典型的解决方案 这样小菜鸡们可以根据这些方案(ACM里面叫板子, 象棋五子棋里叫棋谱, 咱这里叫 设计模式), 略加修改, 这样代码再差也差不到哪里去 … …