初级数据结构(四)——队列

news2024/12/23 19:10:16

   文中代码源文件已上传:数据结构源码

<-上一篇 初级数据结构(三)——栈        |        NULL 下一篇->

        本篇是属于上一篇的补充篇,因为队列和栈的属性特别类似,很多细节部分可以查看上一篇或者初级据结构的第二篇。

1、队列特性

         之前已知,栈结构特性为 LIFO ,队列则是与之相反的先入先出,后入后出,也称为 FIFO ( Fist In Fist Out )。如下图:

         因此,队列与栈的区别只在于弹出顺序,其余完全一致。但是,基于队列的特性,如果选用顺序表实现,则需要不断腾挪数据以填充弹出的头部位置,因此这里最好选用链表来实现以减小计算机资源的开销。

2、文件结构

        仍然是三个文件分模块实现:

        queue.h :用于创建项目的结构体类型以及声明函数;

        queue.c :用于创建队列各种操作功能的函数;

        main.c :仅创建 main 函数,用作测试。

3、队列构建

3.1、代码

        queue.h 中代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define DATAPRT "%d"
typedef int DATATYPE;

typedef struct QueueNode
{
	DATATYPE data;
	struct QueueNode* next;
}QueueNode;

typedef struct QueueInfo
{
	int size;
	QueueNode* head;
	QueueNode* tail;
}QueueInfo;

//函数定义---------------------------------

//队列初始化
extern void QueueInit(QueueInfo*);
//数据入队
extern void QueuePush(QueueInfo*, DATATYPE);
//数据出队
extern void QueuePop(QueueInfo*);
//销毁队列
extern void QueueDestroy(QueueInfo*);
//获取队列数据
extern DATATYPE QueueGetData(QueueInfo*);
//打印队列数据
extern void QueuePrint(QueueInfo*);

        queue.c 中代码:

#include "queue.h"

//队列初始化
void QueueInit(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//初始化
	info->size = 0;
	info->head = NULL;
	info->tail = NULL;
}

//数据入队
void QueuePush(QueueInfo* info, DATATYPE data)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//创建节点
	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	//创建成功验证
	if (!newNode)
	{
		fprintf(stderr, "Malloc Fail\n");
		return;
	}
	//新节点初始化
	newNode->data = data;
	newNode->next = NULL;
	//链接
	if (!info->size)
	{
		info->head = newNode;
		info->tail = newNode;
	}
	else
	{
		info->tail->next = newNode;
		info->tail = newNode;
	}
	info->size++;
}

//数据出队
void QueuePop(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//空表判断
	if (!info->size)
	{
		fprintf(stderr, "Queue Is Empty\n");
	}
	QueueNode* headNode = info->head->next;
	free(info->head);
	info->head = headNode;
	info->size--;
	if (!info->size)
	{
		info->tail = NULL;
	}
}

//销毁队列
void QueueDestroy(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//逐级释放节点
	if (info->size > 0)
	{
		while (info->head != info->tail)
		{
			QueueNode* currentNode = info->head;
			info->head = info->head->next;
			free(currentNode);
		}
		free(info->head);
	}
	QueueInit(info);
}

//获取队列数据
DATATYPE QueueGetData(QueueInfo* info)
{
	//参数有效性验证
	assert(info);
	assert(info->head);
	DATATYPE data = info->head->data;
	QueuePop(info);
	return data;
}

//打印队列数据
void QueuePrint(QueueInfo* info)
{
	//参数有效性验证
	if (!info)
	{
		fprintf(stderr, "Queue Pointer NULL\n");
		return;
	}
	//空队列打印NULL
	if (info->size == 0)
	{
		printf("NULL ");
		return;
	}
	printf(DATAPRT" ", QueueGetData(info));
}

        main.c 中的测试用例:

#include "queue.h"

int main()
{
	QueueInfo info;
	QueueInit(NULL);
	QueueInit(&info);
	QueuePush(&info, 1);
	QueuePush(&info, 2);
	QueuePush(&info, 3);
	QueuePush(&info, 4);
	QueuePush(&info, 5);
	QueuePush(&info, 6);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePush(&info, 1);
	QueuePush(&info, 2);
	QueuePush(&info, 3);
	QueuePush(&info, 4);
	QueuePush(&info, 5);
	QueuePush(&info, 6);
	QueuePop(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePop(&info);
	QueuePrint(&info);
	QueuePush(&info, 3);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueuePrint(&info);
	QueueDestroy(&info);
	return 0;
}

        以上便是全部代码,可自行测试和参考。构建过程中有一些需要注意的点,接下来作阐述。

3.2、构建要点

        1、与常规单链表不同,队列的构建最好以两种不同的结构体进行操作,除了节点信息的结构体之外,需要另外起一个结构体替代单链表的头节点指针。在此结构体内,储存的除了链表头节点指针之外,由于队列的特性,插入数据都是在尾部进行,因此另行创建一个成员变量用于保存尾节点指针。除此之外,为了方便判断队列中的节点个数(常规只判断是否为空队列),最好定义一个成员变量用于储存队列中节点个数。

        2、对于空队列和非空队列的数据入队需要分开讨论。空队列的数据入队需要同时操作头节点及尾节点指针同时指向第一个入队的元素。至于非空队列,需要操作尾节点指针,虽然不再操作头节点指针,但需要另行操作尾节点内部的 next 指针。

        3、数据出队时,如果操作对象时空队列则结束操作,可根据个人喜好自行选择用 assert 警告或者其他方式判断是否为空队列。

        4、销毁队列与单链表的销毁操作一致,直接参考即可。

4、用顺序表构建队列

        虽然不推荐用顺序表,但不代表顺序表不可行。但是在用顺序表构建队列时最好人为地设置阈值以控制什么时候将数组的数据移动至数组前端。

4.1、代码

        文件结构仍与链表实现队列的文件结构一致。代码如下:

        queue.h :

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define DATAPRT "%d"

typedef int DATATYPE;

typedef struct Queue
{
	//数据记录
	size_t size;
	int head;
	int tail;
	size_t capacity;
	//数据
	DATATYPE data[0];
}Queue;

//函数声明------------------------------------------
//初始化创建队列
extern Queue* QueueCreate(void);
//数据入队
extern void QueuePush(Queue*, DATATYPE);
//数据出队
extern void QueuePop(Queue*);
//销毁队列
extern void QueueDestroy(Queue**);
//获取队列数据
extern DATATYPE QueueGetData(Queue*);
//打印队列数据
extern void QueuePrint(Queue*);

        queue.c :

#include "queue.h"

//初始化创建队列
Queue* QueueCreate(void)
{
	//创建队列
	Queue* queue = (Queue*)malloc(sizeof(Queue) + 4 * sizeof(DATATYPE));
	//队列创建结果检查
	if (!queue)
	{
		fprintf(stderr, "Malloc Fail");
		return;
	}
	//队列数据初始化
	queue->size = 0;
	queue->head = -1;
	queue->tail = -1;
	queue->capacity = 4;
	return queue;
}

//数据入队
void QueuePush(Queue* queue, DATATYPE data)
{
	//参数有效性检查
	if (!queue)
	{
		fprintf(stderr, "Queue Address NULL\n");
		return;
	}
	queue->tail++;
	if (queue->tail + 1 >= queue->capacity)
	{
		//队列扩容
		size_t tempSize = (sizeof(Queue) + sizeof(DATATYPE) * queue->capacity * 2);
		Queue* tempQueue = (Queue*)realloc(queue, tempSize);
		//队列扩容结果检查
		if (!tempQueue)
		{
			fprintf(stderr, "Realloc Fail\n");
			return;
		}
		queue = tempQueue;
		queue->capacity *= 2;
	}
	//数据入队
	queue->data[queue->tail] = data;
	//空队列判断
	if (0 == queue->size)
	{
		queue->head = queue->tail;
	}
	queue->size++;
}

//数据出队
void QueuePop(Queue* queue)
{
	//参数有效性检查
	if (!queue)
	{
		fprintf(stderr, "Queue Address NULL\n");
		return;
	}
	//空队列检查
	if (!queue->size)
	{
		fprintf(stderr, "Queue Is Empty\n");
		return;
	}
	queue->head++;
	queue->size--;
	//弹出后空队列检查
	if (!queue->size)
	{
		queue->head = -1;
		queue->tail = -1;
	}
	//空间回收
	if ((queue->size < queue->capacity / 2) && queue->capacity > 4)
	{
		//数据迁移
		for (int i = queue->head, j = 0; i <= queue->tail; i++, j++)
		{
			queue->data[j] = queue->data[i];
		}
		//空间收缩
		size_t tempSize = (sizeof(Queue) + sizeof(DATATYPE) * queue->capacity / 2);
		Queue* tempQueue = (Queue*)realloc(queue, tempSize);
		//队列空间收缩结果检查
		if (!tempQueue)
		{
			fprintf(stderr, "Realloc Fail");
			return;
		}
		//记录迁移
		tempQueue->capacity = queue->capacity / 2;
		tempQueue->head = 0;
		tempQueue->tail = queue->size - 1;
		queue = tempQueue;
	}
}

//销毁队列
void QueueDestroy(Queue** queue)
{
	//参数有效性检查
	if (!(*queue) || !queue)
	{
		fprintf(stderr, "Queue Address NULL\n");
		return;
	}
	free(*queue);
	*queue = NULL;
}

//获取队列数据
DATATYPE QueueGetData(Queue* queue)
{
	//参数有效性检查
	assert(queue);
	//空队列检查检查
	assert(queue->size);
	DATATYPE data = queue->data[queue->head];
	QueuePop(queue);
	return data;
}


//打印队列数据
void QueuePrint(Queue* queue)
{
	//参数有效性检查
	assert(queue);
	//空队列检查检查
	if (!queue->size)
	{
		printf("NULL ");
		return;
	}
	printf(DATAPRT" ", QueueGetData(queue));
}

        main.c 的测试用例:

#include "queue.h"

int main()
{
	Queue* queue = QueueCreate();
	QueuePush(NULL, 1);
	QueuePush(queue, 1);
	QueuePush(queue, 2);
	QueuePush(queue, 3);
	QueuePush(queue, 4);
	QueuePush(queue, 5);
	QueuePush(queue, 6);
	QueuePush(queue, 7);
	QueuePush(queue, 8);
	QueuePush(queue, 9);
	QueuePop(NULL);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePush(queue, 70);
	QueuePush(queue, 72);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePop(queue);
	QueuePop(queue);
	QueuePop(queue);
	QueuePop(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePrint(queue);
	QueuePush(queue, 1);
	QueuePush(queue, 2);
	QueuePush(queue, 3);
	QueuePush(queue, 4);
	QueuePush(queue, 5);
	QueuePush(queue, 6);
	QueuePush(queue, 7);
	QueuePush(queue, 8);
	QueuePush(queue, 9);
	QueuePrint(queue);
	QueuePrint(queue);
	QueueDestroy(&queue);
	QueuePrint(queue);

	return 0;
}

4.2、构建要点

        这里采用的是柔性数组顺序表,当然也可以用结构体包含数组指针的顺序表构建。

        1、对于空队列的初始化,头数据的下标与尾数据的下标应该设置为 -1 ,这点与栈的顶部数据下标设置完全一致。

        2、通过头部元素的数组下标及尾部元素的数组下标来进行队列有效部分内容的控制,出队则头部数组下标 +1 ,入队则数组尾部下标 +1。

        3、数据出队必须进行空队列检查。此外,出队之后对队列中剩余有效元素进行判断,最佳方案是,如果剩余数据元素个数小于开辟空间的一半,则从头部至尾部元素依次腾挪到数组 0 下标位置开始的空间。腾挪完毕后再对过大的空间进行回收。

        4、此外,数据腾挪的时候使用 realloc 有个坑。由于 realloc 的原地扩容和异地扩容是不可控的,如果 realloc 是原地扩容,对临时队列的操作也同样会影响到真实的队列。如上述代码的出队函数,如果用 realloc 创建 tempQueue ,有可能 tempQueue 与 queue 指向同一块空间,所以切不可先对空间进行回收后才进行数据腾挪。

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

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

相关文章

Linux的基本指令和权限的知识

学前的建议&#xff1a;大家不要太关注指令是啥&#xff0c;记不住怎么办&#xff08;没事&#xff0c;想用时去查就好了&#xff09;&#xff0c;这篇文章重点部分是围绕指令的周边知识。毕竟指令是“死肌肉”&#xff0c;而一些关于Linux和操作系统的理论知识才是最重要滴&am…

图片水印怎么去掉?我来教你几招

图片水印怎么去掉&#xff1f;随着自媒体的不断孵化衍生&#xff0c;去水印也成为当下的热门话题之一&#xff0c;每天数以亿计的用户被图片水印所困扰&#xff0c;那么图片水印怎么去掉呢&#xff1f;今天我来教你几招&#xff0c;让你轻松搞定图片水印&#xff0c;一起来学习…

瑞典市场开发攻略,带你走进森林王国

瑞典人口超千万&#xff0c;是一个发达的北欧国家&#xff0c;是欧盟重要的成员国&#xff0c;与我国贸易往来密切&#xff0c;是我国外贸企业非常青睐的市场之一。诺贝尔奖想必大家都知道&#xff0c;诺贝尔就是瑞典人&#xff0c;除了诺贝尔和平奖之外&#xff0c;所有的奖项…

涉密网络的IP查询防护策略

涉密网络的安全性对于维护国家、企业及个人的核心利益至关重要。在当今数字化时代&#xff0c;网络攻击日益猖獗&#xff0c;其中IP查询是攻击者获取目标信息的一种常见手段。本文将探讨涉密网络中防护IP查询的关键策略&#xff0c;以确保网络的机密性和安全性。 1. 专用VPN和…

产品表结构分析

一个项目之中&#xff0c;会有很多数据&#xff0c;众多数据之间也存在这各种关系&#xff0c;如何依据这些关系设计出更符合实际且适合的表及之间的关联关系也是我们所必须学习的 一、常见部门表结构分析 几乎所有框架里面都有一张部门表&#xff0c;我们先来看一下他的结构&…

CanEasy多场景应用,让汽车总线测试更简单

来源&#xff1a;虹科汽车电子 虹科分享 | CanEasy多场景应用&#xff0c;让汽车总线测试更简单 原文链接&#xff1a;https://mp.weixin.qq.com/s/ojic4xfVTLbxXcKlJMGQZw 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 导读 CanEasy是一个基于Windows的总线工具&…

C语言 文件I/O(备查)

所有案列 跳转到其他。 文件打开 FILE* fopen(const char *filename, const char *mode); 参数&#xff1a;filename&#xff1a;指定要打开的文件名&#xff0c;需要加上路径&#xff08;相对、绝对路径&#xff09;mode&#xff1a;指定文件的打开模式 返回值&#xff1a;成…

3.qml 3D-Node类学习

Node类是在View3D 中的对象基础组件&#xff0c;用于表示3D空间中的对象&#xff0c;类似于Qt Quick 2D场景中的Item&#xff0c;介绍如下所示&#xff1a; 如上图可以看到&#xff0c;Node类的子类非常多&#xff0c;比如Model类(显示3D模型)、ParticleSystem3D粒子系统类、Li…

linux 内核同步互斥技术之原子变量

原子变量用来实现对整数的互斥访问&#xff0c;通常用来实现计数器。 例如&#xff0c;我们写一行代码把变量 a 加 1&#xff0c;编译器把代码编译成 3 条汇编指令。 &#xff08;1&#xff09;把变量 a 从内存加载到寄存器。 &#xff08;2&#xff09;把寄存器的值加 1。 &am…

smartKettle离线部署及问题记录

目录 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 部署&#x1f4d7;源码下载&#x1f4d7;后端部署&#x1f4d5;导入后端项目&#x1f4d5;修改settings.xml(自动下载相关jar包)&#x1f4d5; 编译&#x1f4d5; …

【计算机网络】TCP|IP协议

目录 前言 什么是TCP/IP协议&#xff1f; TCP/IP协议的层次结构 TCP/IP协议的工作原理 TCP/IP协议的重要性 结语 前言 TCP/IP协议是当今互联网世界中最重要的网络协议之一&#xff0c;它是网络通信的基石&#xff0c;为数据在网络中的传输提供了可靠性和有效性。本文将深…

比较好的python书籍,python有什么书推荐

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;比较好的python书籍&#xff0c;python有什么书推荐&#xff0c;现在让我们一起来看看吧&#xff01; 我是在半年前接触到Python的&#xff0c;我之前没有一点编程基础&#xff0c;但在我自学的这半年里&#xff0c;我发…

Vue指令之v-on

v-on指令用于注册事件&#xff0c;作用是添加监听与提供事件触发后对应的处理函数。 v-on有两种语法&#xff0c;在提供处理函数的时候既可以直接使用内联语句&#xff0c;也可以提供函数的名字。 第一种语法是直接提供内联语句&#xff0c;如下 v-on:事件名 "内联语句…

数据可视化作用探析

数据可视化是一种将数据转化为图表、图形或其他视觉形式的过程&#xff0c;旨在更直观、更易于理解地展示数据信息。它不仅仅是对数据的简单呈现&#xff0c;更是一种利用视觉化手段帮助人们理解数据、发现模式、分析趋势和做出决策的强大工具。今天&#xff0c;我就从可视化从…

RocketMQ可视化工具 打包遇到的yarn intall 问题

文章目录 RocketMQ可视化工具1.github上下载2.修改参数3.运行4.打包5.出错6.解决7.重试8.再解决9.很奇怪运行没错&#xff0c;但是测试错啦10.不想深究&#xff0c;直接跳过测试11.展示成功 RocketMQ可视化工具 1.github上下载 下载地址 https://github.com/apache/rocketmq-…

在Windows 11中打开任务管理器的7种方法,总有一种很适用

​本文介绍了如何在Windows 11中打开任务管理器。使用Windows任务管理器,你可以跟踪系统进程、监视资源使用情况和强制停止应用程序。 如何使用搜索栏打开任务管理器 在Windows 11中访问任务管理器的一种更简单的方法是使用搜索栏。 1、按Windows键+S或选择任务栏中的“搜索…

Nodejs 第二十二章(脚手架)

编写自己的脚手架 那什么是脚手架&#xff1f; 例如:vue-cli Angular CLI Create React App 编写自己的脚手架是指创建一个定制化的工具&#xff0c;用于快速生成项目的基础结构和代码文件&#xff0c;以及提供一些常用的命令和功能。通过编写自己的脚手架&#xff0c;你可以…

值类型相关函数与对象类型相关函数内存调用过程

值类型相关函数内存调用&#xff1a; 先来看这样一段代码&#xff0c;你认为它的运行结果是多少呢&#xff1f; 20和11还是20和10&#xff1f; package org.example;public class Main {public static void main(String[] args) {int a10;add(a);System.out.println(a);}pub…

【INTEL(ALTERA)】Agilex7 FPGA Development Kit DK-DEV-AGI027R1BES编程/烧录/烧写/下载步骤

DK-DEV-AGI027R1BES 的编程步骤&#xff1a; 将外部 USB Blaster II 连接到 J10- 外部 JTAG 接头。将交换机 SW5.3 设置为 ON&#xff08;首次&#xff09;。打开 英特尔 Quartus Prime Pro Edition 软件编程工具。单击 硬件设置 &#xff0c;然后选择 USB Blaster II。将硬件…

Unity_使用FairyGUI搭建登录页面

Unity_使用FairyGUI搭建登录页面 1. 使用FairyGUI准备一个UI界面&#xff0c;例如&#xff1a;以下登录 2. 发布导出&#xff08;发布路径设置为Unity的Asset下任何路径&#xff09; 3. Unity编辑器安装FairyGUI包资源&#xff08;在资源商店找见并存储为我的资源&#xff0c;…