【数据结构】队列——超详解!!!(包含队列的实现)

【数据结构】队列------超详解!!!(包含队列的实现)

前言

往期我们的学习中讲到了顺序表、链表以及栈
它们可以帮我们解决很多问题,而类似的数据结构还有很多
今天,我们就来聊聊------队列

一、队列是什么?

1. 队列的定义

队列:

特殊的线性表只允许在一端进行插入数据操作,在另一端进行删除数据操作的一种数据结构

队列具有先进先出FIFO(First In First Out) 的原则

2. 队尾&&队头

进行插入操作的一端称为队尾
进行删除操作的一端称为队头

3.类比

就像我们在餐厅吃饭时,若饭店满人了,也许就会开始排队摇号,有1号、2号、3号...等等

当有座位空出来时,就让序号前的人先去;当又有人来时,就在队尾加一个序列
而队列也是如此,只允许在一端进行插入数据操作,在另一端进行删除数据操作的一种数据结构
当要删除数据时,就让队头的数据(先来的数据)先删除;当要插入数据时,就在队尾插入数据

二、队列的实现

那么,我们该如何来实现队列呢?

1. 用什么来实现?

想要实现队列,首先要想清楚要用什么实现队列

之前,我们依次实现了顺序表、链表以及栈,他们都是用数组或链表来实现的

队列也可以数组和链表的结构实现

但是之前我们讲到了在数组上头删(出队列)很麻烦,后续的每一个元素都要往前挪一位
如果使用数组的结构来实现队列,出队列就会在数组头上出数据,效率会很低
综上所诉,使用链表的结构来实现队列更优一些

2. 实现思路

由于队列与单链表类似,这里可以拿单链表的实现来做参考

  • 第1步

与单链表同理,队列中的每一个节点有以下两成员:
1 . 指向下一个节点的指针
2 . 节点中存储的数据

  • 第2步

但是,我们需要记录下队列的总元素个数、头节点、尾节点这三个重要信息
于是乎,我们可以单独创建一个结构体来包含这些信息,将队列的总元素个数、头节点、尾节点包含在内

  • 第3步

最后,就是一一实现我们队列的增、删、查、改等操作了

3. 代码实现

本文以创建一个 int 类型的队列为例

(1)创建头文件&源文件

之前在讲解扫雷游戏中我就提到:
在写复杂程序时要养成写多个头文件&源文件的好习惯,这样条理就很清晰也不会乱

详见 【C语言】扫雷游戏详解(包含递归,变色,记录时间等)

如图:

创建了一个 " Queue.h "头文件
用于存放用来放函数的声明和一些库函数的头文件

创建了一个 " Queue.c "源文件
用于用来放函数的定义(队列的主体)

还有一个 " Test.c "源文件
用于测试实现的队列的运行效果

(2)定义队列(定义)

首先我们要定义一个队列

这里之前讲过,要先创建一个结构体来表示每个节点
再单独创建一个结构体来包含这些信息,将队列的总元素个数、头节点、尾节点包含在内

这样方便我们对队列的实现

代码演示:(内有注释)
(在头文件" Queue.h "中写)

c 复制代码
//重定义,方便修改类型
typedef int QDataType;

//表示每个节点的类型
typedef struct QListNode
{
	QDataType data;//节点中存储的数据
	struct QListNode* next;//指向下一个节点的指针
	
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;//头指针
	QNode* tail;//尾指针
	int size;  //总元素个数
}Queue;

在定义队列的代码中,有两个需要注意的点:

  1. 本文是以 int 类型为例,但如果以后要将队列中的元素类型修改成 char 类型或是其他类型一个一个修改就很麻烦
    所以我们重定义int类型为QDataType,并将接下来代码中的int类型全部写成QDataType
    这是为了方便我们以后对类型进行修改,仅需将int 改为其他类型即可
  2. 在定义队列的同时重定义队列的变量名为 QNode方便以后使用

(3)队列的初始化(初始化)

定义完队列后,肯定要对队列进行初始化,内容全部置 0 / NULL

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 初始化队列 
void QueueInit(Queue* q);

" Queue.c "源文件中写到:

c 复制代码
// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	//断言空指针

	q->front = NULL;
	q->tail = NULL;
	q->size = 0;
	//全部初始化置 0 / NULL
}

在写实现代码中,有一个很重要的点:

当我们函数在进行传参时,可能会传入空指针,而我们知道空指针是不能进行解引用的
故为了我们的代码更加健壮,可以加入assert 断言来判断是否符合条件,在之后的代码中也都有

关于更加详细的assert 断言介绍可参见下文:
【C语言】带你层层深入指针------指针详解3(野指针、assert等)

(4)队列的销毁(销毁)

在我们的程序运行完毕后,当然要对队列进行销毁,以免占用内存
这里我们直接遍历链表,一一释放空间销毁就行啦

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 销毁队列 
void QueueDestroy(Queue* q);

" Queue.c "源文件中写到:

c 复制代码
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	//断言空指针

	QNode* cur = q->front;

	//遍历链表,一一释放空间销毁
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = q->tail = NULL;
	q->size = 0;
	//全部初始化置 0 / NULL
}

(5)队尾入队列(尾插)

  • 怎么插入?

在入队列时,由于队列只允许在一端进行插入数据操作,在另一端进行删除数据
所以我们在插入时进行尾插数据
而在删除时进行头删数据

  • 怎么开辟空间?

由于这里是链表的结构,故每一次插入时开辟一个节点的空间就行了

  • 注意!!!

注意:当队列中没有节点时
此时插入的节点既是头节点,又是尾节点
所以头尾节点都要赋值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);

" Queue.c "源文件中写到:

c 复制代码
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	//断言空指针

	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	//直接开辟一个节点的空间

	if (tmp == NULL)
	//加一个 if语句 防止增容失败
	{
		perror("malloc");
		return;
	}

	//没有问题后就赋值
	tmp->data = data;
	tmp->next = NULL;

	//注意:当队列中没有节点时
	//此时插入的节点既是头节点,又是尾节点
	if (q->size == 0)
	{
		q->front = tmp;
		q->tail = tmp;
	}
	else
	{
		q->tail->next = tmp;
		q->tail = tmp;
	}
	q->size++;
}

(6)队头出队列 (头删)

  • 怎么删除?

在出队列时,由于队列只允许在一端进行插入数据操作,在另一端进行删除数据
刚刚我们在队尾进行插入数据
而现在删除数据时就是用头删数据

  • 注意!!!

注意:当队列中只有一个节点时
此时头尾节点相等,都要进行删除
所以将头节点和尾节点都释放

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 队头出队列 
void QueuePop(Queue* q);

" Queue.c "源文件中写到:

c 复制代码
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	//断言空指针
	//断言顺序表不能为空
	
	//注意:当队列中只有一个节点时,头尾节点相等
	//此时将头节点和为尾节点都释放
	if (q->size == 1)
	{
		free(q->front);
		q->front = NULL;
		q->tail = NULL;
	}
	else
	{
		QNode* del = q->front;
		q->front = q->front->next;
		free(del);
	}
	q->size--;
}

(7)获取队列头部元素

这个很简单,直接用下标进行访问数据
再返回所对应的值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 获取队列头部元素 
QDataType QueueFront(Queue* q);

" Queue.c "源文件中写到:

c 复制代码
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	//断言空指针
	//断言顺序表不能为空

	return q->front->data;
}

(8)获取队列尾部元素

这个很简单,直接用下标进行访问数据
再返回所对应的值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);

" Queue.c "源文件中写到:

c 复制代码
// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	//断言空指针
	//断言顺序表不能为空

	return q->tail->data;
}

(9)获取队列中有效元素个数

这个很简单
直接返回所对应的值

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 获取队列中有效元素个数 
int QueueSize(Queue* q);

" Queue.c "源文件中写到:

c 复制代码
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	//断言空指针
	
	return q->size;
}

(10)检测队列是否为空

这个很简单
如果队列为空返回非零结果
如果不为空返回0

代码演示:(内有注释)
(其中 q 是一个指向队列的指针,下同)

" Queue.h "头文件中写到:

c 复制代码
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps);

" Queue.c "源文件中写到:

c 复制代码
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	//断言空指针
	
	return q->size == 0;
}

三、完整代码实现

1. Queue.h

用于存放用来放函数的声明和一些库函数的头文件

c 复制代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
#include<sperror.h>

//重定义,方便修改类型
typedef int QDataType;

//表示每个节点的类型
typedef struct QListNode
{
	QDataType data;//节点中存储的数据
	struct QListNode* next;//指向下一个节点的指针
	
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;//头指针
	QNode* tail;//尾指针
	int size;  //总元素个数
}Queue;

// 初始化队列 
void QueueInit(Queue* q);

// 队尾入队列 
void QueuePush(Queue* q, QDataType data);

// 队头出队列 
void QueuePop(Queue* q);

// 获取队列头部元素 
QDataType QueueFront(Queue* q);

// 获取队列队尾元素 
QDataType QueueBack(Queue* q);

// 获取队列中有效元素个数 
int QueueSize(Queue* q);

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);

// 销毁队列 
void QueueDestroy(Queue* q);

2. Queue.c

用于用来放函数的定义(队列的主体)

c 复制代码
#include"Queue .h"

// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);
	//断言空指针

	q->front = NULL;
	q->tail = NULL;
	q->size = 0;
	//全部初始化置 0 / NULL
}

// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	//断言空指针

	QNode* tmp = (QNode*)malloc(sizeof(QNode));
	//直接开辟一个节点的空间

	if (tmp == NULL)
	//加一个 if语句 防止增容失败
	{
		perror("malloc");
		return;
	}

	//没有问题后就赋值
	tmp->data = data;
	tmp->next = NULL;

	//注意:当队列中没有节点时
	//此时插入的节点既是头节点,又是尾节点
	if (q->size == 0)
	{
		q->front = tmp;
		q->tail = tmp;
	}
	else
	{
		q->tail->next = tmp;
		q->tail = tmp;
	}
	q->size++;
}

// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	//断言空指针
	//断言顺序表不能为空
	
	//注意:当队列中只有一个节点时,头尾节点相等
	//此时将头节点和尾节点都释放
	if (q->size == 1)
	{
		free(q->front);
		q->front = NULL;
		q->tail = NULL;
	}
	else
	{
		QNode* del = q->front;
		q->front = q->front->next;
		free(del);
	}
	q->size--;
}

// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	//断言空指针
	//断言顺序表不能为空

	return q->front->data;
}

// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->size > 0);
	//断言空指针
	//断言顺序表不能为空

	return q->tail->data;
}

// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	//断言空指针
	
	return q->size;
}

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	//断言空指针
	
	return q->size == 0;
}

// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	//断言空指针

	QNode* cur = q->front;

	//遍历链表,一一释放空间销毁
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	q->front = q->tail = NULL;
	q->size = 0;
	//全部初始化置 0 / NULL
}

3. Test.c

用于测试实现的队列的运行效果
(这里是小编在写代码时写的测试用例)
(大家在写的时候也要多多测试哦)

c 复制代码
#include"Queue .h"

int main()
{
	Queue Q;

	QueueInit(&Q);

	QueuePush(&Q, 1);
	QueuePush(&Q, 2);
	QueuePush(&Q, 3);
	QueuePush(&Q, 4);
	QueuePush(&Q, 5);
	QueuePush(&Q, 6);

	/*QueuePop(&Q);
	int ret1 = QueueFront(&Q);

	QueuePop(&Q);
	int ret2 = QueueFront(&Q);

	QueuePop(&Q);
	int ret3 = QueueFront(&Q);

	printf("%d %d %d\n\n", ret1, ret2, ret3);*/

	while (QueueEmpty(&Q))
	{
		printf("%d ", QueueFront(&Q));
		QueuePop(&Q);
	}

	printf("\n\n");

	QueueDestroy(&Q);

	return 0;
}

结语

本期资料来自于:

https://legacy.cplusplus.com/

OK,本期的队列详解到这里就结束了

若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持

本文有若有不足之处,希望各位兄弟们能给出宝贵的意见。谢谢大家!!!

新人,本期制作不易希望各位兄弟们能动动小手,三连走一走!!!

支持一下(三连必回QwQ)

相关推荐
雾岛听蓝1 小时前
C++ 模板初阶
开发语言·c++
小杰帅气2 小时前
智能指针喵喵喵
开发语言·c++·算法
soft20015252 小时前
MySQL Buffer Pool深度解析:冷热数据分离下的LRU链表工作机制
数据库·mysql·链表
特立独行的猫a2 小时前
cpp-linenoise介绍——让命令行拥有猫一般的敏捷
c++·ui·命令行·cpp-linenoise
南棱笑笑生2 小时前
20251219给飞凌OK3588-C开发板适配Rockchip原厂的Buildroot【linux-5.10】后解决启动不了报atf-2的问题
linux·c语言·开发语言·rockchip
Vizio<2 小时前
STM32HAL库开发笔记-系统定时器与中断闪灯
笔记·stm32·单片机
智驱力人工智能2 小时前
守护生命的水上之眼 无人机人员落水检测系统的技术攻坚与应用实践 无人机溺水识别 山区水库无人机落水检测系统 水域安全无人机部署指南
大数据·人工智能·算法·安全·无人机·边缘计算
hweiyu002 小时前
排序算法选型决策树
算法·排序算法
可爱又迷人的反派角色“yang”4 小时前
ansible剧本编写(三)
linux·网络·云计算·ansible