【数据结构】线性表--队列

news2025/5/20 1:43:04

【数据结构】线性表--队列

  • 一.什么是队列
  • 二.队列的实现
    • 1.队列结构定义:
    • 2.队列初始化函数:
    • 3.队列销毁函数:
    • 4.入队列函数(尾插):
    • 5.出队列函数(头删):
    • 6.取队头元素:
    • 7.取队尾元素:
    • 8.求队长:
    • 9.判空:
  • 三.总结

一.什么是队列

情景引入:
你们在用电脑时有没有经历过,机器有时会处于疑似死机的状态,鼠标点什么似乎都没用,双击任何快捷方式都不动弹。就当你失去耐心,打算reset时,突然它像酒醒了一样,把你刚才单击的所有操作全部都按顺序执行了一遍。这是因为操作系统在当时可能CPU一时忙不过来,等前面的事忙完后,后面多个指令需要通过一个通道输出,按先后次序排队执行造成的结果。
再比如像移动、联通、电信等客服电话,客服人员与客户相比总是少数,在所有的客服人员都占线的情况下,客户会被要求等待,直至有某个客服人员空下来,才能让最先等待的客户接通电话。这里也是将所有当前拨打客服电话的客户进行了排队处理。
操作系统和客服系统中,都是应用了一种数据结构来实现刚才提到的先进先出的排队功能,这就是队列。

概念理解:
队列也是一种操作受限的线性表,与栈不同的是,队列要求在一端插入数据,在另一端删除数据(就类似于日常生活中的排队一样)。
我们将插入数据的一端称为队尾,删除数据的一端称为队头。队列满足FIFO(first in first out)原则。如图所示:
在这里插入图片描述

二.队列的实现

队列也有两种表示方法:①顺序存储②链式存储。

顺序存储即采用数组做为底层结构,由于在一端插入数据,另一端删除数据,此时需要大量移动元素。

链式存储可以使用单链表,也可以使用双向链表。
使用单链表时,可以将链表头作为队头,链表尾作为队尾,此时删除数据的操作即是头删,时间复杂度为O(1),但是插入数据时,需要进行频繁的尾插操作,时间复杂度为O(n),因此可以再设立一个尾指针指向最后一个结点,避免每次尾插需要遍历整个链表,优化时间复杂度为O(1)。
使用双链表时,即头插尾删,或头删尾插。
本文使用单链表实现队列。

1.队列结构定义:

因为使用单链表来实现队列,所以先定义出结点结构。结点需要存储数据本身信息,以及保存下一个结点的位置信息。

typedef int QDataType;

typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QNode;

因为队列需要进行频繁的尾插操作,所以再定义一个结构,用头指针和尾指针来保存队列的头结点和尾结点的信息,以优化尾插操作的时间复杂度。

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

2.队列初始化函数:

队列初始化即将头指针和尾指针均置为空即可。

//初始化函数
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
}

3.队列销毁函数:

队列的销毁即通过头指针来找到各个结点,将结点空间依次释放,最后再将头尾指针再次置空。

//销毁函数
void QueueDestory(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

4.入队列函数(尾插):

数据元素入队列:先以元素的值申请一个新结点,然后通过尾指针找到最后一个结点,将新结点链接到最后一个结点的next指针,最后再将尾指针指向新结点。

//入队列函数
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//处理申请空间失败的情况
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//队列为空时,将头尾指针指向同一个结点
	if (pq->head == NULL)
		pq->head = pq->tail = newnode;
	else
	{
		//先将新结点链接到队列的队尾
		pq->tail->next = newnode;
		//再将尾指针指向新结点
		pq->tail = newnode;
	}
}

5.出队列函数(头删):

出队列时需要注意先判断队列是否为空,当队列为空时不能出数据,可以使用assert断言,也可以直接返回。
不为空时通过头指针找到第一个结点,再顺着找到下一个结点(若下一个结点为空,则代表此时链表只有一个结点,直接删除),释放掉第一个结点,并将头指针指向下一个结点。

//出队列函数
void QueuePop(Queue* pq)
{
	assert(pq);
	
	//队列为空时不能出数据(或者使用断言)
	if (pq->head == NULL)
	{
		return;
	}
	else
	{
		//先记录头指针的下一个结点
		//需要先判断下一个结点是不是空
		if (pq->head->next == NULL)
		{
			free(pq->head);
			pq->head = pq->tail = NULL;
		}
		else
		{
			QNode* next = pq->head->next;

			//释放头指针指向的结点
			free(pq->head);
			//将头指针指向下一个结点,成为新的队头
			pq->head = next;
		}
	}
}

6.取队头元素:

取队头元素也要注意判断队列是否为空,使用断言判断,不为空直接返回头指针指向第一个结点的值。

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);//队为空不能取数据

	return pq->head->data;
}

7.取队尾元素:

取队尾元素也要注意判断是否为空,使用断言判断。不为空时直接返回尾指针指向结点的值。

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->head);//队为空不能取数据

	return pq->tail->data;
}

8.求队长:

求队长直接用一个int变量,通过头指针依次遍历到尾指针,该变量++即可。

//求队长(数据个数)
int QueueSize(Queue* pq)
{
	assert(pq);

	int size = 0;
	QNode* cur = pq->head;
	while (cur != NULL)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

9.判空:

直接看头指针是否为空即可判断队列是否为空。

//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->head == NULL;//队列为空返回true,否则返回false
}

三.总结

除了上述实现的普通队列,还有循环队列、优先队列、双端队列、阻塞队列等队列,感兴趣可以上网搜索一下相关实现。
以下是本文全部源码:
Queue.h:

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>

typedef int QDataType;

typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

//初始化函数
void QueueInit(Queue* pq);

//销毁函数
void QueueDestory(Queue* pq);

//入队列函数
void QueuePush(Queue* pq, QDataType x);

//出队列函数
void QueuePop(Queue* pq);

//取队头数据
QDataType QueueFront(Queue* pq);

//取队尾数据
QDataType QueueBack(Queue* pq);

//求队长(数据个数)
int QueueSize(Queue* pq);

//判空
bool QueueEmpty(Queue* pq);

Queue.c:

#include"Queue.h"

//初始化函数
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
}

//销毁函数
void QueueDestory(Queue* pq)
{
	assert(pq);

	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

//入队列函数
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	//处理申请空间失败的情况
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//队列为空时,将头尾指针指向同一个结点
	if (pq->head == NULL)
		pq->head = pq->tail = newnode;
	else
	{
		//先将新结点链接到队列的队尾
		pq->tail->next = newnode;
		//再将尾指针指向新结点
		pq->tail = newnode;
	}
}

//出队列函数
void QueuePop(Queue* pq)
{
	assert(pq);
	
	//队列为空时不能出数据(或者使用断言)
	if (pq->head == NULL)
	{
		return;
	}
	else
	{
		//先记录头指针的下一个结点
		//需要先判断下一个结点是不是空
		if (pq->head->next == NULL)
		{
			free(pq->head);
			pq->head = pq->tail = NULL;
		}
		else
		{
			QNode* next = pq->head->next;

			//释放头指针指向的结点
			free(pq->head);
			//将头指针指向下一个结点,成为新的队头
			pq->head = next;
		}
	}
}

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);//队为空不能取数据

	return pq->head->data;
}

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->head);//队为空不能取数据

	return pq->tail->data;
}

//求队长(数据个数)
int QueueSize(Queue* pq)
{
	assert(pq);

	int size = 0;
	QNode* cur = pq->head;
	while (cur != NULL)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

//判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->head == NULL;//队列为空返回true,否则返回false
}

test.c:

//测试各个函数功能
void test()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueueDestory(&q);
}

int main()
{
	test();
	return 0;
}

感谢阅读!^_^
在这里插入图片描述

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

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

相关文章

[Vue3]语法变动

Vue3的语法相对比Vue2有不少改变&#xff0c;这篇讲一下基础语法在Vue3里的形式。 创建Vue对象 在脚手架项目中&#xff0c;index.html等资源不再编写代码&#xff0c;只作为一个容器。所有的页面代码都在.vue相关文件中进行编写&#xff0c;由main.js引入各个.vue文件渲染出页…

Ubuntu服务器开启SNMP服务 监控系统配置指南 -优雅草星云智控简易化操作

Ubuntu服务器开启SNMP服务 & 监控系统配置指南 -优雅草星云智控简易化操作 一、Ubuntu服务器开启SNMP服务 步骤1&#xff1a;安装SNMP服务 sudo apt update sudo apt install snmp snmpd snmp-mibs-downloader -y 步骤2&#xff1a;配置SNMP&#xff08;编辑配置文件&am…

linux本地部署ollama+deepseek过程

1.Tags ollama/ollama GitHub 选择一个版本下载&#xff0c;我下的是0.5.12 2.tar解压该文件 3.尝试启动ollama ollama serve 4.查看ollama的版本 ollama -v 5.创建一个系统用户 ollama&#xff0c;不允许登录 shell&#xff0c;拥有一个主目录&#xff0c;并且用…

从零开始实现大语言模型(十五):并行计算与分布式机器学习

1. 前言 并行计算与分布式机器学习是一种使用多机多卡加速大规模深度神经网络训练过程&#xff0c;以减少训练时间的方法。在工业界的训练大语言模型实践中&#xff0c;通常会使用并行计算与分布式机器学习方法来减少训练大语言模型所需的钟表时间。 本文介绍PyTorch中的一种…

OpenCV进阶操作:指纹验证、识别

文章目录 前言一、指纹验证1、什么是指纹验证2、流程步骤 二、使用步骤&#xff08;案例&#xff09;三、指纹识别&#xff08;案例&#xff09;1、这是我们要识别的指纹库2、这是待识别的指纹图3、代码4、结果 总结 前言 指纹识别作为生物识别领域的核心技术之一&#xff0c;…

网络安全-等级保护(等保) 2-5 GB/T 25070—2019《信息安全技术 网络安全等级保护安全设计技术要求》-2019-05-10发布【现行】

################################################################################ GB/T 22239-2019 《信息安全技术 网络安全等级保护基础要求》包含安全物理环境、安全通信网络、安全区域边界、安全计算环境、安全管理中心、安全管理制度、安全管理机构、安全管理人员、安…

3D生成新突破:阶跃星辰Step1X-3D开源,可控性大幅提升

Step1X-3D 是由 StepFun 联合 LightIllusions 推出的新一代 高精度、高可控性 3D资产生成框架。基于严格的 数据清洗与标准化流程&#xff0c;我们从 500万 3D资产 中筛选出 200万高质量数据&#xff0c;构建了 标准化的几何与纹理属性数据集&#xff0c;为3D生成提供更可靠的训…

MySQL数据类型之VARCHAR和CHAR使用详解

在设计数据库字段时&#xff0c;字符串类型算是最常见的数据类型之一了&#xff0c;这篇文章带大家深入探讨一下MySQL数据库中VARCHAR和CHAR数据类型的基本特性&#xff0c;以及它们之间的区别。 VARCHAR类型 VARCHAR&#xff08;Variable Character&#xff0c;可变长度字符…

《Docker 入门与进阶:架构剖析、隔离原理及安装实操》

1 docker 简介 1.1 Docker 的优点 Docker 是一款开放平台&#xff0c;用于应用程序的开发、交付与运行&#xff0c;能将应用和基础架构分离&#xff0c;实现软件快速交付 &#xff0c;还能以统一方式管理应用和基础架构&#xff0c;缩短代码从编写到上线的时间。其核心优势如…

基于Akamai云计算平台的OTT媒体点播转码解决方案

点播视频&#xff08;VOD&#xff09;流媒体服务依赖于视频流的转码来高效分发内容。在转码工作流程中&#xff0c;视频被转换为适合观看设备、网络条件和性能限制的格式。视频转码是计算密集型过程&#xff0c;因此最大化可用硬件上可转码的视频流数量是首要考虑因素。不同基础…

【MySQL】02.数据库基础

1. 数据库的引入 之前存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件存储存在安全性问题&#xff0c;文件不利于数据查询和管理&#xff0c;文件不利于存储海量数据&#xff0c;文件在程序中控制不方便。而为了解决上述问题&#xff0c;专家们设计出更加利于…

选错方向太致命,华为HCIE数通和云计算到底怎么选?

现在搞HCIE的兄弟越来越多了&#xff0c;但“数通和云计算&#xff0c;到底考哪个&#xff1f;”这问题&#xff0c;依旧让不少人头疼。 一个是华为认证的老牌王牌专业——HCIE数通&#xff0c;稳、系统、岗位多&#xff1b; 一个是新趋势方向&#xff0c;贴合云原生、数字化…

经典启发算法【早期/启发式/HC爬山/SA模拟退火/TS禁忌搜/IA免疫 思想流程举例全】

文章目录 一、早期算法二、启发式算法三、爬山法HC3.1 基本思路3.2 伪代码 四、模拟退火SA4.1 算法思想4.2 基本流程4.3 再究原理4.3.1 Metropolis准则4.3.2 再理解 4.4 小Tips4.5 应用举例4.5.1 背包问题&#xff1a;分析&#xff1a;求解&#xff1a; 4.5.2 TSP问题&#xff…

IntraWeb 16.0.2 + Bootstrap 4 居中布局实战(附源码+效果图)

前言 最近在优化一个 IntraWeb 16.0.2 项目时&#xff0c;发现默认布局方式不够灵活&#xff0c;尤其是在不同屏幕尺寸下对齐效果不佳。于是&#xff0c;我决定引入 Bootstrap 4 来实现 完美居中布局&#xff0c;并成功落地&#xff01;今天就把完整的 源代码 实际效果图 分享…

【Java ee初阶】jvm(3)

一、双亲委派机制&#xff08;类加载机制中&#xff0c;最经常考到的问题&#xff09; 类加载的第一个环节中&#xff0c;根据类的全限定类名&#xff08;包名类名&#xff09;找到对应的.class文件的过程。 JVM中进行类加载的操作&#xff0c;需要以来内部的模块“类加载器”…

23种设计模式考试趋势分析之——适配器(Adapter)设计模式——求三连

文章目录 一、考点分值占比与趋势分析二、真题考点深入挖掘三、"wwwh"简述四、真题演练与解析五、极简备考笔记 适配器模式核心要点六、考点记忆顺口溜七、多角度解答 一、考点分值占比与趋势分析 由于知识库提供的真题年份信息不完整&#xff0c;我们仅能对现有数据…

【Linux笔记】——线程互斥与互斥锁的封装

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;Linux &#x1f339;往期回顾&#x1f339;&#xff1a;【Linux笔记】——Linux线程封装 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、线程互斥的概念二、互…

Android屏幕采集编码打包推送RTMP技术详解:从开发到优化与应用

在现代移动应用中&#xff0c;屏幕采集已成为一个广泛使用的功能&#xff0c;尤其是在实时直播、视频会议、远程教育、游戏录制等场景中&#xff0c;屏幕采集技术的需求不断增长。Android 平台为开发者提供了 MediaProjection API&#xff0c;这使得屏幕录制和采集变得更加简单…

【深度学习】残差网络(ResNet)

如果按照李沐老师书上来&#xff0c;学完 VGG 后还有 NiN 和 GoogLeNet 要学&#xff0c;但是这两个我之前听都没听过&#xff0c;而且我看到我导师有发过 ResNet 相关的论文&#xff0c;就想跳过它们直接看后面的内容。 现在看来这不算是不踏实&#xff0c;因为李沐老师说如果…

《Python星球日记》 第94天:走近自动化训练平台

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、自动化训练平台简介1. Kubeflow Pipelines2. TensorFlow Extended (TFX) 二…