探索数据结构:链式队与循环队列的模拟、实现与应用

news2024/11/26 8:44:34

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty’s blog

1. 队列的定义

队列(queue)是一种只允许在一端进行插入操作,而在另一端进行删除操作的线性表。其严格遵循先进先出(First In First Out)的规则,简称FIFO

img

img

  • 队头(Front):允许删除的一端,又称队首。
  • 队尾(Rear):允许插入的一端。

2. 队列的分类

队列与栈类似,实现方式有两种。一种是以数组的方式实现,另一种以单链表来实现。这两种实现方式各有优劣,并且都有细节需要处理。

  1. 基于单链表实现:我们可以将链表的头节点与尾节点分别作为队列的队首与队尾,这样我们就能用两个指针来对其进行操作。如下图:

img

  1. 基于数组实现:我们同样可以通过两个下标分别指向数组的起始与结束,但这时我们就可能发现两个问题:
  • 问题一:在不断出队与进队得到过程中,起始下标与末尾下标都在向后移动,当两个下标同时指向数组末尾时就无法再移动了,并且**浪费前面大量空间,**如图1

  • 问题二:为了解决上述问题,我们将数组首尾相接变为循环数组。但这时又会出现一个问题,那便是当队首与队尾下标指向同一个节点时,这个队列到底是还是呢?这时我们有三个解决方法

  • 第一种:牺牲一个单元来区分队空和队满,这时若队列不为空,让队尾下标指向队尾的下一个位置。约定以队头指针在队尾指针的下一位置作为队满的标志,即Q->rear+1==Q->front。如图二。

  • 第二种:增设表示元素个数的数据成员 size 。这样,队空的条件为 Q->size==0;队满的条件为 Q->size==MaxSize

  • 第三种:增加表示队满的数据成员flag。将flag初始化为0,当队满时将其置为1。

img

3. 队列的功能

  1. 队列的初始化。
  2. 判断队列是否为空。。
  3. 返回队头与队尾的元素。
  4. 返回队列的大小。
  5. 入队与出队。
  6. 打印队列的元素。
  7. 销毁队列。

4. 队列的声明

4.1. 链式队

链式队的声明十分简单,参照上面图我们就可以直接实现了。

typedef int QDataType;
typedef struct QueueNode 
{
	QDataType data;
	struct QueueNode* next;
}QNode;
typedef struct Queue 
{
	QNode* front;
	QNode* rear;
	size_t size;
}Queue;

4.2. 循环队

根据上述分析,我们采用一个数组来实现队列,其声明如下

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
#define MAXSIZE 50  //定义元素的最大个数
/*循环队列的顺序存储结构*/
typedef struct {
    QDataType data[MAXSIZE];
    int front;  //头指针
    int rear;   //尾指针
}Queue;

void QueueInit(Queue* q);//初始化队列
bool QueueEmpty(Queue* q);//判断是否为空
QDataType QueueFront(Queue* q);//获取队头元素
QDataType QueueBack(Queue* q);//或许队尾元素
size_t QueueSize(Queue* q);//或许队列长度
void QueuePush(Queue* q, QDataType x);//入队
void QueuePop(Queue* q);//出队
void QueuePrint(Queue* q);//打印队列元素

5. 队列的初始化

对队列声明的数据进行初始化,防止随机值。

5.1. 链式队

void QueueInit(Queue* q)
{
	q->front = NULL;
	q->rear = NULL;
	q->size = 0;
}

5.2. 循环队

void QueueInit(Queue* q) 
{
    q->front = 0;
    q->rear = 0;
}

5.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

6. 判断队列是否为空

判断队列是否为空十分简单,这里就不在赘述。

6.1. 链式队

bool QueueEmpty(Queue* q)
{
	assert(q);
	return (q->front == NULL) && (q->rear == NULL);
}

6.2. 循环队

bool QueueEmpty(Queue*q)
{
    assert(q);
    return q->front == q->rear;
}

6.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

7. 返回队头与队尾元素

因为定义了头指针与尾指针,所以访问数据也十分方便。

7.1. 链式队

QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->front->data;
}
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->rear->data;
}

7.2. 循环队

QDataType QueueFront(Queue* q)
{
    assert(q);
    assert(!QueueEmpty(q));
    return q->data[q->front];
}
QDataType QueueBack(Queue* q)
{
    assert(q);
    assert(!QueueEmpty(q));
    return q->data[q->rear-1];
}

7.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

8. 队列的大小

8.1. 链式队

size_t QueueSize(Queue* q)
{
	return q->size;
}

8.2. 循环队

求循环队列的大小,我们很容易想到用Q->rear-Q->front得出队列元素个数。但是我们要考虑到一种特殊情况:当队列先删除元素再添加元素时,末尾下标**rear**可能循环重置,如下图。

img

那到底该如何解决这个问题呢?其实我们只需要在原来基础上加上一个MAXSIZE就行了,为了使图一情况也适用我们仍需模上一个MAXSIZE

size_t QueueSize(Queue*q) 
{
    assert(q);
    return (q->rear - q->front + MAXSIZE) % MAXSIZE;
}

8.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

9. 入队

9.1. 链式队

链式队列入队时需要判断队列是否为空的特殊情况,如果是则还需要将尾指针也指向这个节点。

void QueuePush(Queue* q, QDataType x)
{
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	newnode->data = x;
	newnode->next = NULL;
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	if (q->front == NULL)
	{
		q->front = q->rear = newnode;
	}
	else
	{
		q->rear->next = newnode;
		q->rear = newnode;
	}
	q->size++;
}

9.2. 循环队

为了使循环队列在插入数据时实现循环操作,我们可以每次进行取模操作。

void QueuePush(Queue* q, QDataType x) 
{
    assert(q);
    q->data[q->rear] = x;   
    q->rear = (q->rear + 1) % MAXSIZE;  //rear指针向后移一位置,若到最后则转到数组头部
}

9.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

10. 出队

10.1. 链式队

同样考虑特殊情况,防止队列为空。并且当队列只有一个节点时需要将头指针与尾指针都置为空。

void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	//1.只有一个结点
	if (q->front == q->rear)
	{
		free(q->front);
		q->front = q->rear = NULL;
	}
	//2.有多个结点
	else
	{
		QNode* del = q->front;
		q->front = q->front->next;
		free(del);
		del = NULL;
	}
	q->size--;
}

10.2. 循环队

同样为了实现循环,我们可以进行取模操作。

 void QueuePop(Queue* q)
 {
     assert(q);
     assert(!QueueEmpty(q));
    q->front = (q->front + 1) % MAXSIZE;    //front指针向后移一位置,若到最后则转到数组头部
 }

10.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

11. 打印队列

11.1. 链式队

void QueuePrint(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	QNode* tail = q->rear;
	printf("队头->");
	while (cur != tail->next)
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("队尾\n");
}

11.2. 循环队

 void QueuePrint(Queue* q)
 {
     assert(q);
     int cur = q->front;
     printf("队头->");
     while (cur != q->rear)
     {
         printf("%d->", q->data[cur]);
         cur = (cur + 1) % MAXSIZE;
     }
     printf("队尾\n");
 }

11.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:无论是链式队还是循环队列花费空间都是一个固定大小,所以空间复杂度为O(1)。

12. 销毁队列

12.1. 链式队

void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
	q->front = q->rear = NULL;
}

12.2. 循环队

循环队列是以数组作为存储空间,并不是动态内存开辟的空间,所以并不需要手动释放空间。

12.3. 复杂度分析

  • 时间复杂度:无论是链式队还是循环队列花费时间都是一个常数,所以时间复杂度为O(1)。
  • 空间复杂度:链式队花费空间都是一个固定大小,所以空间复杂度为O(1)。

13. 链式队与循环队列的对比与应用

13.1. 对比

对比项链式队循环队列
时间效率因为存在头指针与尾指针,所以链式队的出队与入队的时间都相对较小。循环队列是基于数组实现的,支持下标的随机访问,所以时间消耗也并不大
空间效率链式队每次入队都需固定创造一个新的节点,空间利用率较高,较稳定。循环队列的空间是固定的,可能会造成空间的浪费。

13.2. 应用

队列的应用与栈一样,十分广泛

  1. 当我们去食堂扫码订餐时,你的订单就会加入一个队列中。
  2. 在操作系统中,队列可以用来管理任务进度与进程切换。

14. 完整代码

14.1. 链式队

14.1.1. Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode 
{
	QDataType data;
	struct QueueNode* next;
}QNode;
typedef struct Queue 
{
	QNode* front;
	QNode* rear;
	size_t size;
}Queue;
void QueueInit(Queue* q);//初始化队列
bool QueueEmpty(Queue* q);//判断是否为空
QDataType QueueFront(Queue* q);//获取队头元素
QDataType QueueBack(Queue* q);//或许队尾元素
size_t QueueSize(Queue* q);//或许队列长度
void QueuePush(Queue* q, QDataType x);//入队
void QueuePop(Queue* q);//出队
void QueuePrint(Queue* q);//打印队列元素
void QueueDestroy(Queue* q);//销毁队列
14.1.2. Queue.c
#include"Queue.h"
void QueueInit(Queue* q)
{
	q->front = NULL;
	q->rear = NULL;
	q->size = 0;
}
bool QueueEmpty(Queue* q)
{
	assert(q);
	return (q->front == NULL) && (q->rear == NULL);
}
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->front->data;
}
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->rear->data;
}
size_t QueueSize(Queue* q)
{
	return q->size;
}
void QueuePush(Queue* q, QDataType x)
{
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	newnode->data = x;
	newnode->next = NULL;
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	if (q->front == NULL)
	{
		q->front = q->rear = newnode;
	}
	else
	{
		q->rear->next = newnode;
		q->rear = newnode;
	}
	q->size++;
}
void QueuePop(Queue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	//1.只有一个结点
	if (q->front == q->rear)
	{
		free(q->front);
		q->front = q->rear = NULL;
	}
	//2.有多个结点
	else
	{
		QNode* del = q->front;
		q->front = q->front->next;
		free(del);
		del = NULL;
	}
	q->size--;
}
void QueuePrint(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	QNode* tail = q->rear;
	printf("队头->");
	while (cur != tail->next)
	{
		printf("%d->",cur->data);
		cur = cur->next;
	}
	printf("队尾\n");
}
void QueueDestroy(Queue* q)
{
	assert(q);
	QNode* cur = q->front;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
		del = NULL;
	}
	q->front = q->rear = NULL;
}

14.2. 循环队列

14.2.1. Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
#define MAXSIZE 50  //定义元素的最大个数
/*循环队列的顺序存储结构*/
typedef struct {
    QDataType data[MAXSIZE];
    int front;  //头指针
    int rear;   //尾指针
}Queue;

void QueueInit(Queue* q);//初始化队列
bool QueueEmpty(Queue* q);//判断是否为空
QDataType QueueFront(Queue* q);//获取队头元素
QDataType QueueBack(Queue* q);//或许队尾元素
size_t QueueSize(Queue* q);//或许队列长度
void QueuePush(Queue* q, QDataType x);//入队
void QueuePop(Queue* q);//出队
void QueuePrint(Queue* q);//打印队列元素
14.2.2. Queue.c
void QueueInit(Queue* q) 
{
    q->front = 0;
    q->rear = 0;
}
bool QueueEmpty(Queue*q)
{
    assert(q);
    return q->front == q->rear;
}
QDataType QueueFront(Queue* q)
{
    assert(q);
    assert(!QueueEmpty(q));
    return q->data[q->front];
}
QDataType QueueBack(Queue* q)
{
    assert(q);
    assert(!QueueEmpty(q));
    return q->data[q->rear-1];
}
size_t QueueSize(Queue*q) 
{
    assert(q);
    return (q->rear - q->front + MAXSIZE) % MAXSIZE;
}
void QueuePush(Queue* q, QDataType x) 
{
    assert(q);
    q->data[q->rear] = x;   
    q->rear = (q->rear + 1) % MAXSIZE;  //rear指针向后移一位置,若到最后则转到数组头部
}
 void QueuePop(Queue* q)
 {
     assert(q);
     assert(!QueueEmpty(q));
    q->front = (q->front + 1) % MAXSIZE;    //front指针向后移一位置,若到最后则转到数组头部
 }
 void QueuePrint(Queue* q)
 {
     assert(q);
     int cur = q->front;
     printf("队头->");
     while (cur != q->rear)
     {
         printf("%d->", q->data[cur]);
         cur = (cur + 1) % MAXSIZE;
     }
     printf("队尾\n");
 }

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

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

相关文章

系统分析师-参考模型

前言 网络术语中的参考模型指的是OSI参考模型&#xff0c;由ISO&#xff08;国际标准化组织&#xff09;制定的一套普遍适用的规范集合&#xff0c;以使得全球范围的计算机平台可进行开放式通信。 ISO创建了一个有助于开发和理解计算机的通信模型&#xff0c;即开放系统互联OS…

openwrt 编译mysql数据库固件,并调用

前言 openwrt 编译源码mysql数据库&#xff0c;并编写demo调用 一、整体架构设计 作者要做一个项目&#xff0c;没有后端服务&#xff0c;只有一个电脑&#xff0c;需要在电脑上安装mysql服务端。然后在设备上安装mysql客户端。 二、PC安装mysql 1.官网链接 自行百度安装&a…

一文彻底搞懂spring循环依赖

文章目录 1. 什么是循环依赖2. Spring怎么解决循环依赖3. 无法处理的循环依赖 1. 什么是循环依赖 Spring 中的循环依赖是指两个或多个 Bean 之间相互依赖&#xff0c;形成一个循环引用的情况。在 Spring 容器中&#xff0c;循环依赖通常指的是单例&#xff08;Singleton&#…

使用 Idea 快速搭建 SpringMVC 项目的详细步骤

一、开篇 SpringMVC 是一款当下流行的优秀的 MVC 框架&#xff0c;关于 MVC 的概念、作用、优点等内容介绍&#xff0c;在作者之前的一篇 Chat 《深入理解 MVC 框架原理&#xff1a;自定义 Struts2 框架》中有详细的描述。描述了关于另一款主流 MVC 框架的原理介绍&#xff0c;…

Docker-Container

Docker ①什么是容器②为什么需要容器③容器的生命周期容器 OOM容器异常退出容器暂停 ④容器命令清单总览docker createdocker rundocker psdocker logsdocker attachdocker execdocker startdocker stopdocker restartdocker killdocker topdocker statsdocker container insp…

unrealbuildtool 无法找到,执行 Generate Visual Studio Project 错误

参考链接 Generate cpp project Couldnt find UnrealBuildTool - Pipeline & Plugins / Plugins - Epic Developer Community Forums (unrealengine.com) 错误提示如下图&#xff1a; 解决方案&#xff1a; 打开 UnrealBuildTool&#xff0c;生成解决方案就可以了

学习Fast-LIO系列代码中相关概念理解

目录 一、流形和流形空间&#xff08;姿态&#xff09; 1.1 定义 1.2 为什么要有流形? 1.3 流形要满足什么性质&#xff1f; (1) 拓扑同胚 (2) 可微结构 1.4 欧式空间和流形空间的区别和联系? (1) 区别&#xff1a; (2) 联系&#xff1a; 1.5 将姿态定义在流形上比…

【Python实用标准库】argparser使用教程

argparser使用教程 1.介绍2.基本使用3.add_argument() 参数设置4.参考 1.介绍 &#xff08;一&#xff09;argparse 模块是 Python 内置的用于命令项选项与参数解析的模块&#xff0c;其用主要在两个方面&#xff1a; 一方面在python文件中可以将算法参数集中放到一起&#x…

CavalierContours 二维线操作

CavalierContours 二维线操作 2D polyline library for offsetting, combining, etc. 用于偏移、交并补等组合等操作的 2D 多折段线库。 Polyline Structure 多段线结构 Polylines are defined by a sequence of vertexes and a bool indicating whether the polyline is cl…

关系型数据库mysql(7)sql高级语句①

目录 一.MySQL常用查询 1.按关键字&#xff08;字段&#xff09;进行升降排序 按分数排序 &#xff08;默认为升序&#xff09; 按分数升序显示 按分数降序显示 根据条件进行排序&#xff08;加上where&#xff09; 根据多个字段进行排序 ​编辑 2.用或&#xff08;or&…

【C语言】内存函数(memmove)的使用和模拟实现

目录 前言memmove定义1.在cplusplus中的定义 memmove的模拟实现1、思路2、难点3、解决方法 模拟实现代码 前言 这篇文章讲述了memcpy的使用、模拟实现和一个未解决的问题内存函数(memcpy)的使用和模拟实现 当我们使用我们模拟的my_memcpy拷贝&#xff0c;当源拷贝地址与目标拷…

C语言编译与链接

前言 我们想一个问题&#xff0c;我们写的C语言代码都是文本信息&#xff0c;电脑能直接执行c语言代码吗&#xff1f;肯定不能啊&#xff0c;计算机能执行的是二进制指令&#xff0c;所以将C语言转化为二进制指令需要一段过程&#xff0c;这篇博客讲一下编译与链接&#xff0c;…

ISAC代码仿真学习笔记

文章目录 A. MIMO Communication ModelB. MIMO Radar Model III. Joint Waveform and Phase Shift Matrix Design for Given Radar BeampatternA. Problem FormulationB. Proposed Algorithm IV. JOINT DESIGN WITH TRADE-OFF BETWEEN RADAR AND COMMUNICATION PERFORMANCEA. P…

boost::asio::ip::tcp/udp::socket::release 函数为什么限制 Windows 8.1 才可以调用?

如本文题目所示&#xff0c;这是因为只有在 Windows 8.1&#xff08;Windows Server 2012 RC&#xff09;及以上 Windows 操作版本才提供了运行时&#xff0c;修改/删除完成端口关联的ABI接口。 boost::asio 在 release 函数底层实现之中是调用了 FileReplaceCompletionInform…

Ubuntu20安装python3.10

1、添加 deadsnakes PPA 到源列表 add-apt-repository ppa:deadsnakes/ppa apt update 2、安装 apt install python3.10 3设置默认版本为 Python3.10 查看所有python版本 ls -l /usr/bin/python* update-alternatives --install /usr/bin/python3 python3 /usr/bin/pytho…

java-pytorch 使用手动下载FashionMNIST数据集进行测试

java-pytorch 使用手动下载FashionMNIST数据集进行测试 先定义训练数据和测试数据的位置查看一下读取到的标签数据格式使用loc和iloc访问下数据&#xff0c;便于下面操作使用read_image函数查看下图片的数据大小开始写数据集使用DataLoader去加载我们自己的数据看下加载后的dat…

亚马逊跨境电商迎来崭新时代,武汉星起航携手卖家共赴新征程

随着全球经济一体化的深入发展&#xff0c;跨境电商已成为推动国际贸易的重要力量。据最新数据显示&#xff0c;2023年中国跨境电商出口规模达到1.83万亿元&#xff0c;同比增长19.6%&#xff0c;增速远超电商行业整体水平。在这一背景下&#xff0c;2024年有望成为中国跨境电商…

ForkJoinPool、CAS原子操作

ForkJoinPool ForkJoinPool是由JDK1.7后提供多线程并行执行任务的框架。可以理解为一种特殊的线程池。 1.任务分割&#xff1a;Fork&#xff08;分岔&#xff09;&#xff0c;先把大的任务分割成足够小的子任务&#xff0c;如果子任务比较大的话还要对子任务进行继续分割。 …

构造器练习

练习一 题目 案例&#xff1a; (1)定义Student类,有4个属性&#xff1a;String name;int age;String school;String major;(2)定义Student类的3个构造器:- 第一个构造器Student(String n, int a)设置类的name和age属性&#xff1b; - 第二个构造器Student(String n, int a, St…

大话设计模式之策略模式

策略模式是一种行为设计模式&#xff0c;它允许在运行时选择算法的行为。这种模式定义了一族算法&#xff0c;将每个算法都封装起来&#xff0c;并且使它们之间可以互相替换。 在策略模式中&#xff0c;一个类的行为或其算法可以在运行时改变。这种模式包含以下角色&#xff1…