【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

news2024/11/24 6:30:40

文章目录

  • 前言
  • 队列实现
    • 带头结点单向队列
    • 不带头结点单向队列
    • 循环队列
  • 总结

前言

  当我们编写程序时,经常需要处理各种数据结构。队列是一种常见的数据结构,它有着广泛的应用场景。队列的基本操作包括入队和出队,应用于模拟等待队列、消息队列、计算机缓存等场合。

  在实际编程中,我们可以用不同的数据结构来实现队列。本文主要介绍了三种不同的队列实现方式,包括带头结点单向队列、不带头结点单向队列和循环队列。这些队列实现方式分别使用了链表、数组等不同的数据结构,在实现细节、时间复杂度和空间利用率等方面具有不同的特点。对于程序员来说,了解不同的队列实现方式,可以更好地选择适合自己应用场景的队列实现方式,提高程序的效率。

队列实现

  队列是一种先进先出(First-In-First-Out, FIFO)的数据结构,它的基本操作包括入队(将元素插入队尾)和出队(将队首元素删除)。

带头结点单向队列

  • 带头结点单向队列是一种使用链表实现的队列,与普通链表不同的是,带头结点单向队列在链表头部添加一个不存储数据的节点,作为链表的头结点,用于方便队列的操作。

  • 在带头结点单向队列中,入队操作是将新元素插入到链表尾部,出队操作是将队首元素的后继节点作为新的队首节点。

  • 由于链表的特性,带头结点单向队列的入队操作是 O ( 1 ) O(1) O(1) 的,而出队操作需要遍历整个链表,因此其时间复杂度为 O ( n ) O(n) O(n),其中 n n n 表示队列的元素个数。

  下面展示一张图表示队列插入的逻辑:
在这里插入图片描述

  队列节点结构体设计:

// 节点结构体
struct queueNode
{
	// 数据元素
	int elem;
	// 指向下一个元素
	struct queueNode *next;
};

struct queue
{
	// 尾部
	struct queueNode *rear;
	// 头部
	struct queueNode *front;
	// 长度
	int lenth;
};

  代码实现:

/********************** 含头结点 ***************/
// 队列初始化
struct queue*queueInit()
{
	// 申请空间并且初始化
	struct queue*res = (struct queue*)malloc(sizeof(struct queue));
	// 初始化一个虚拟节点
	struct queueNode*node = (struct queueNode*)malloc(sizeof(struct queueNode));
	res->front = node;
	res->rear = node;
	res->lenth = 0;
	return res;
}

// 入队列
void pushQueueNode(struct queue*queue, int elem)
{
	// 新建一个节点
	struct queueNode*node = (struct queueNode*)malloc(sizeof(struct queueNode));
	node->elem = elem;
	node->next = NULL;

	// 添加到尾部
	queue->rear->next = node;
	queue->rear = node;
	queue->lenth++;
}

// 出队列
bool pullQueueNode(struct queue*queue, int&elem)
{
	// 判断队列是否为空
	if (queueIsEmpty(queue))
		return true;

	// 移除队列中的首元素
	struct queueNode*p = queue->front->next;
	elem = p->elem;
	queue->front->next = p->next;
	queue->lenth--;
	// 队列中只有一个元素应该改变队尾指针
	if (queue->rear == p)
		queue->rear = queue->front;
	free(p);
	return false;
}

// 判断队列是否空
bool queueIsEmpty(struct queue*queue)
{
	return queue->lenth == 0;
}

// 打印队列
void outPutQueue(struct queue*queue)
{
	// 判断队列是否为空
	if (queue->lenth == 0)
		return;
	struct queueNode*p = queue->front->next;
	// 遍历队列并打印
	printf("queue:");
	while (p)
	{
		printf("%d ",p->elem);
		p = p->next;
	}
	printf("\n");
}

不带头结点单向队列

  • 与带头结点单向队列相比,不带头结点单向队列不使用头结点,直接从链表的第一个节点开始存储数据。

  • 在不带头结点单向队列中,入队操作是将新元素插入到链表尾部,出队操作是删除链表的头结点,并将头结点的后继节点作为新的队首节点。

  • 由于每次出队操作都需要删除链表的头结点,因此不带头结点单向队列中的实现会导致频繁的内存分配和释放,效率比较低。

  其插入操作的图解为:
在这里插入图片描述

  队列节点结构体设计:

struct queueNode
{
	// 数据元素
	int elem;
	// 指向下一个元素
	struct queueNode *next;
};

  代码实现:

/********************** 不含头结点 ***************/
// 队列初始化
struct queue*vQueueInit()
{
	// 申请空间并且赋值
	struct queue*res = (struct queue*)malloc(sizeof(struct queue));
	res->lenth = 0;
	res->front = res->rear = NULL;
	return res;
}

// 打印队列
void vOutPutQueue(struct queue*queue)
{
	printf("queue:");
	struct queueNode*p = queue->front;
	// 遍历打印
	while (p)
	{
		printf("%d ",p->elem);
		p = p->next;
	}
	printf("\n");
}

// 入队列
void vPushQueueNode(struct queue*queue, int elem)
{
	// 申请一个节点
	struct queueNode *p = (struct queueNode*)malloc(sizeof(struct queueNode));
	p->elem = elem;
	p->next = NULL;
	// 队列为空时入队列
	if (queue->lenth == 0) 
	{
		queue->front = p;
		queue->rear = p;
	}
	else
	{
		// 先将节点接到队列上
		queue->rear->next = p;
		// 再移动队列尾指针
		queue->rear = p;
	}
	queue->lenth++;
}

// 出队列
bool vPullQueueNode(struct queue*queue, int&elem)
{
	// 判断队列是否为空
	if (vQueueIsEmpty(queue))
		return false;

	// 获取队列首元素 并返回
	struct queueNode*p = queue->front;
	elem = p->elem;
	queue->front = p->next;
	queue->lenth--;

	return true;
}

// 判断队列是否空
bool vQueueIsEmpty(struct queue*queue)
{
	return queue->lenth == 0;
}

  与含头结点队列操作上的区别:主要在于入队列与出队列时需要判断队列是否为空。

循环队列

  • 循环队列是一种使用数组实现的队列,与普通数组不同的是,循环队列的队尾指针和队首指针可以在达到数组的末尾位置时循环到数组的开头位置。

  • 在循环队列中,需要使用两个指针分别指向队首和队尾,或者使用一个指针和队列长度维护队首和队尾位置。

  • 在循环队列中,入队操作是将新元素插入到队尾,如果队列满了,则会返回队列已满的错误;出队操作是将队首元素删除,并将队首指针指向下一个元素。

  • 循环队列的主要优点是空间利用率比较高,可以实现环形存储,而且操作的时间复杂度比带头结点单向队列和不带头结点单向队列都要高效,都是 O ( 1 ) O(1) O(1)

  队列结构体设计:

// 队列的最大长度
#define QUEUEMAXSIZE 10
// 队列结构体
struct queueCirculate
{
	// 存储数据
	int data[QUEUEMAXSIZE];
	// 记录数量
	int count;
	// 记录队列队头
	int front;
	// 记录队列队尾
	int rear;
};

  队列节点结构体设计:

******************** 循环队列 ******************/
// 初始化循环队列
struct queueCirculate*queueCirculateInit(void)
{
	// 申请空间
	struct queueCirculate*res = (struct queueCirculate*)malloc(sizeof(struct queueCirculate));
	// 初始化队列的数量 队头队尾的位置
	res->count = 0;
	res->front = 0;
	res->rear = 0;

	return res;
}

// 入队列
bool pushQueueCirculate(struct queueCirculate*queue,int elem)
{
	// 判断队列是否已满
	if (queue->count == QUEUEMAXSIZE)
		return false;
	// 入队列
	queue->data[queue->rear] = elem;
	// 改变队尾指针的位置 以及 队列的数量
	queue->count++;
	if (queue->count < QUEUEMAXSIZE)
		queue->rear = (queue->rear + 1) % QUEUEMAXSIZE;
	return true;
}

// 出队列
bool pullQueueCirculate(struct queueCirculate*queue, int&elem)
{
	// 判断队列是否为空
	if (queue->count == 0)
		return false;
	// 出队列
	elem = queue->data[queue->front];
	// 移动队头指针 并且 改变队列元素数量
	queue->front = (queue->front + 1) % QUEUEMAXSIZE;
	queue->count--;
	return true;
}

// 队列是否为空
bool queueCirculateIsEmpty(struct queueCirculate*queue)
{
	return queue->count == 0;
}

// 打印队列
void outPutQueueCirculate(struct queueCirculate*queue)
{
	int count = queue->count;
	printf("queue: ");
	for (int i = 0; i < count; ++i)
	{
		int temp = -1;
		pullQueueCirculate(queue, temp);
		printf("%d ", temp);
	}
	printf("\n");
}

总结

  带头结点单向队列、不带头结点单向队列和循环队列各有优缺点,适用于不同的应用场景:

  带头结点单向队列适用于需要实现入队操作频繁、出队操作较少的场景。它可以利用链表的特性,实现入队操作的时间复杂度为 O(1),缺点是出队操作的时间复杂度为 O(n)。

  不带头结点单向队列适用于队列长度较短的需要入队和出队操作都比较频繁的场景。由于不使用头结点,可以节省一些空间,但出队操作需要频繁的内存分配和释放,效率比较低。

  循环队列适用于队列长度较大且入队和出队操作都比较频繁的场景。它的环形存储结构可以充分利用数组的连续存储空间,实现了空间的高效利用。同时,入队和出队操作的时间复杂度都为 O(1),效率比较高。但是,循环队列需要事先定义一个最大长度,如果队列长度超过了最大长度,需要进行扩容操作。此外,由于环形结构的特殊性,实现起来也比较复杂。

  因此,在选择队列实现方式时,需要根据具体的应用场景综合考虑时间复杂度、空间利用率和实现难度等因素,选择最适合自己的队列实现方式。

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

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

相关文章

LVS-DR集群

LVS-DR集群 一.LVS-DR工作原理 1.数据包流向 数据包流向分析&#xff1a; &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &…

Flowable工作流入门完整SpringBoot案例

文章目录 一 、Flowable 的出现是为了什么二、Flowable 的优势三、常见的Java类/实例3.1 ProcessEngine3.2 RepositoryService3.3 ProcessDefinition3.4 Deployment3.5 RuntimeService3.6 ProcessInstance3.7 TaskService3.8 JavaDelegate3.9 其他 四、核心数据库表4.1 数据库4…

CET4写译学习

学习记录笔记&#xff1a; 05.四级写译技巧(上)_哔哩哔哩_bilibili 不会的东西不要往上写。寻找可以替换的词。 保证写的所有内容都是正确的。 切题&#xff0c;论证清楚。 要有自己的观点&#xff0c;然后去论证。 词汇&#xff0c;语法&#xff0c;句子结构都整好。 文…

【软件测试】接口测试工具APIpost

说实话&#xff0c;了解APIpost是因为&#xff0c;我的所有接口相关的文章下&#xff0c;都有该APIpost水军的评论&#xff0c;无非就是APIpost是中文版的postman&#xff0c;有多么多么好用&#xff0c;虽然咱也还不是什么啥网红&#xff0c;但是不知会一声就乱在评论区打广告…

Linux日志

rsyslog系统日志管理 哪类程序产生的什么日志放到什么地方 处理日志的进程 第一类&#xff1a; rsyslogd&#xff1a;系统专职日志程序&#xff0c;处理绝大部分日志记录&#xff0c;系统操作相关的信息&#xff0c;如登录信息&#xff0c;程序启动关闭相关信息&#xff0c…

C#语言实现4K图片放大缩小和平移显示性能的速度测试

在介绍“熊猫视图.Net图形控件”系列文章中&#xff0c; 【“熊猫视图.Net图形控件”介绍链接】https://blog.csdn.net/mosangbike/article/details/126026801有对显示图像文件的测试结果&#xff0c;当时测试的不太严谨。今天抽时间详细测试了一下。 从网上找了一张Jpg图像作…

亲宝宝 实习 面经

目录 1.char varchar 长度是字符数还是字节数 编码格式2.整型数据类型3.decimal及其实现4.慢查询5.索引失效6.explain7.for foreach性能差异8.数据库事务隔离级别9.binlog redolog 二阶段提交10.redis数据类型11.redis实现消息队列12.mybatis传参方法13.insert返回主键 1.char …

数据库实验一 数据库和数据表的建立、修改和删除

任务描述 本关任务&#xff1a;建立数据库 为了完成本关任务&#xff0c;你需要掌握&#xff1a; 如何创建数据库&#xff0c;显示已经建立的数据库 相关知识 创建数据库 创建数据库是在系统磁盘上划分一块区域用于数据的存储和管理。 命令格式&#xff1a; CREATE DATABA…

粮油智能制造MES追溯系统源码

粮油生产加工MES追溯系统源码 粮油生产加工MES追溯系统&#xff0c;实现从种植、加工、检验、销售各个环节的数据采集。 粮油MES质量管控防伪溯源系统可广泛用于粮油生产加工领域。实现种植主体、种植基地、生产计划、压榨、精炼、包装、销售、物料管理、检验检测等各个环节的…

Java对象拷贝MapStruct

介绍 编译期即可生成对象复制代码。简单理解&#xff0c;功能定位org.springframework.beans.BeanUtils。 官网&#xff0c;GitHub-MapStruct。 入门 maven项目引入依赖&#xff1a; mapstruct&#xff1a;包含必要注解&#xff0c;如Mappingmapstruct-processor&#xff1…

Cereal 靶机

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;bcj2 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2021.1 信息收集 1.探测目标靶机 arp-scan -l 2.nmap -p- -A -T4 192.168.1.107 探测目标靶机开放端口和服务 漏…

我是如何入门网络安全?有什么自学心得?

我是如何入门&#xff0c;网络安全的 那年我高三毕业的时候要填志愿前几天 我妈问我想学什么专业。 我说&#xff0c;想学网络设计、或者计算机、网络安全工程师 那时候还比较年轻&#xff0c;也对网络&#xff0c;计算机这方面感兴趣嘛 于是我妈和我爸决定让我学网管。 我…

卡尔曼滤波与组合导航原理(十三)无迹卡尔曼滤波UKF

一、蒙特卡洛仿真 蒙特卡洛仿真的基本原理是通过生成大量的随机样本&#xff0c;以近似地估计实际事件的概率和预测结果。 它是以蒙特卡洛赌场命名的&#xff0c;因为它使用随机数和概率统计的方法来模拟现实世界中的各种情况和结果&#xff0c;就像在赌场中抛骰子或发牌一样。…

LeetCode_Day6 | 有效的字母异位词、两个数组的交集、快乐数、两数之和!

LeetCode_哈希表 242.有效的字母异位词1.题目描述2.题解 349.两个数组的交集1.题目描述2.题解 202.快乐数1.题目描述2.题解思路(官方题解啊&#xff01;看了好几遍真难) 3.算法4.代码实现5.复杂度分析 1.两数之和1.题目描述2.哈希表法3.代码实现 242.有效的字母异位词 1.题目描…

《三》TypeScript 中函数的类型

TypeScript 允许指定函数的参数和返回值的类型。 函数声明的类型定义&#xff1a;function 函数名(形参: 形参类型, 形参: 形参类型, ...): 返回值类型 {} function sum(x: number, y: number): number {return x y } sum(1, 2) // 正确 sum(1, 2, 3) // 错误。输入多余的或者…

CAN总线竞争与仲裁机制分析

1、CAN总线的接口 (1)CAN总线是串行、差分信号、异步总线&#xff0c;传输数据用两根信号线组成的差分信号线&#xff1b; (2)CANH和CANL是一组双绞线&#xff0c;两根线的电平差值表示逻辑1和0&#xff1b; 2、CAN总线显性、隐形电平 显性电平代表逻辑0&#xff0c;隐形电平代…

【Prometheus】mysqld_exporter+Grafana+AlertManager预警

环境 prometheus-2.44.0 mysqld_exporter-0.14.0 grafana-enterprise-9.1.2-1.x86_64.rpm alertmanager-0.25.0. 简介 mysql_exporter是用来收集MysQL或者Mariadb数据库相关指标的&#xff0c;mysql_exporter需要连接到数据库并有相关权限。既可以用二进制安装部署&#xff0c;…

08.JavaWeb-SpringMVC

2.SpringMVC Spring框架是一个开源的轻量级框架&#xff0c;SpringMVC是Spring的一个子框架 2.1SpringMVC工作机制 2.1.1 DispatcherServlet前端控制器 【不需要程序员开发】 作用&#xff1a;作为接受请求&#xff0c;响应结果&#xff0c;相当于转发器&#xff0c;中央处理…

『Jenkins』最新版Jenkins安装与Git集成—CentOS 7安装的详细教程

&#x1f4e3;读完这篇文章里你能收获到 图文形式安装Jenkins在Jenkins集成Git并进行的配置感谢点赞收藏&#xff0c;避免下次找不到~ 文章目录 一、准备工作1. 安装Java Development Kit (JDK 11) 二、安装Jenkins1. 下载和安装最新版的Jenkins2. 启动Jenkins服务3. 将Jenkin…

【线性代数】

求解线性方程组 右乘向量/矩阵 把左边的矩阵拆成一个个列向量&#xff0c;右边的向量表示对左边列向量组的线性组合。 [ c o l 1 c o l 2 c o l 3 ] [ 3 4 5 ] [ 3 c o l 1 4 c o l 2 5 c o l 3 ] \left[\begin{array}{c} col_{1} & col_{2} & col_{3} \end{array}\…