【数据结构与算法】使用单链表实现队列:原理、步骤与应用

news2024/11/15 11:53:43

   

            💓 博客主页:倔强的石头的CSDN主页 

           📝Gitee主页:倔强的石头的gitee主页

            ⏩ 文章专栏:《数据结构与算法》

                                  期待您的关注

1b7335aca73b41609b7f05d1d366f476.gif

目录

一、引言

🎄队列的概念

🎄为什么要用单链表实现队列

二、单链表前情回顾

三、队列的结构定义

🍃单个元素的结构定义

🍃队列的结构定义

🍃图解单链表与队列的关系

四、队列的接口实现

🌾初始化

🌾销毁

🌾入队列(队尾插入)

🌾出队列(队头删除)

🌾获取队首元素

🌾获取队尾元素

🌾获取队列元素个数

🌾队列判空

五、C语言实现代码

Queue.h   队列头文件

Queue.c    队列实现文件

test.c        main函数测试文件

测试结果

六、应用场景

七、总结


一、引言

🎄队列的概念

队列(Queue)是一种特殊类型的线性数据结构,它遵循特定的操作顺序。队列的基本操作通常是在一端添加元素(称为入队或enqueue),在另一端移除元素(称为出队或dequeue)。这种操作特性使得队列符合“先进先出”(FIFO, First In First Out)的原则。

基本概念

  1. 先进先出(FIFO)原则:这是队列的核心特性。它意味着最早被添加到队列中的元素将是第一个被移除的元素。这个原则确保了数据处理的顺序性。

  2. 队头(Front):队列中第一个被添加的元素位于队头,但它不是永远位于队列的第一个位置,而是指按照入队顺序,最先应该被出队的元素的位置。在出队操作中,总是从队头移除元素。

  3. 队尾(Rear)或队末:新元素总是被添加到队列的这一端。在入队操作中,新元素总是被放置在队尾。

  4. 队列为空:当队列中没有元素时,称队列为空队列。

  5. 队列满:在某些实现中,特别是使用静态数组实现的队列,当队列无法再添加新元素时,称为队列满。但在使用链表实现的队列中,通常不会遇到队列满的情况,因为链表可以动态扩展。

队列提供了一种有效的方式来管理和处理需要按照特定顺序执行的任务或数据项。通过使用队列,可以确保数据项按照它们被接收或生成的顺序进行处理,这是许多应用中非常关键的要求。

🎄为什么要用单链表实现队列

  1. 动态内存分配
    单链表节点是动态分配的,这意味着队列的大小可以根据需要动态地增长和缩小。与静态数组实现的队列不同,单链表队列不需要预先定义最大的容量,从而避免了因队列容量不足而导致的内存溢出问题。

  2. 高效操作
    在单链表队列中,入队(enqueue)操作通常只需要在链表尾部添加一个节点,时间复杂度为O(1)。出队(dequeue)操作也只需要删除链表头部的节点,时间复杂度同样为O(1)。这使得单链表队列在处理大量数据时具有较高的效率。而数组不方便头插或头删,不管将数组的首部当作队首还是队尾都会降低效率

  3. 内存利用率
    单链表队列在添加和删除元素时,只需要分配或释放单个节点的内存,而不需要像数组那样可能需要分配或释放整个数据块的内存。这有助于减少内存碎片,提高内存利用率。另外队列只需要对首尾元素进行操作,带尾指针的单链表已经足够高效的进行插入删除,相比双向链表节省了一半的指针空间。

  4. 灵活性
    单链表队列在结构上相对灵活,可以根据具体需求进行扩展或修改。例如,可以在节点中添加额外的信息以支持更复杂的操作,或者在链表中插入或删除节点以实现特定的功能。

  5. 易于实现
    单链表的基本操作(如添加节点、删除节点等)相对简单,因此使用单链表实现队列也相对容易。这使得初学者能够更容易地理解和实现队列数据结构。

  6. 适应性强
    在实际应用中,队列可能会遇到各种复杂的情况,如并发访问、数据异常等。单链表队列由于其动态性和灵活性,能够较好地适应这些复杂情况。例如,在并发环境下,可以使用锁或其他同步机制来确保队列操作的原子性;在数据异常时,可以通过遍历链表来检查和处理异常情况。

二、单链表前情回顾

参考文章:

【C语言项目实战】使用单链表实现通讯录-CSDN博客

三、队列的结构定义

🍃单个元素的结构定义

  • 数据部分
  • 指向下一个元素的指针next
// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

🍃队列的结构定义

  • 指向队首元素的指针phead
  • 指向队尾元素的指针ptail
  • 队列的元素个数size
// 队列的结构 
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

将队列的首尾指针封装成一个结构体,可以方便函数调用,统一接口
另外使用一个整型变量记录元素个数,利于其他函数功能实现

🍃图解单链表与队列的关系

 

四、队列的接口实现

🌾初始化

  • 对形参判空(接收的地址必须是有效的(队列必须存在)
  • 队列的首尾指针初始化为NULL
  • size变量初始化为0
// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);//接收的地址必须是有效的(队列必须存在)
	q->head = q->tail = NULL;
	q->size = 0;
}

🌾销毁

  • 对形参判空
  • 创建指针循环遍历链表:     每次记录下指针的下一个节点,释放指针指向的节点,指针指向下一个节点
  • 出循环后,将首尾指针指向NULL
  • size置0
// 销毁队列 
void QueueDestroy(Queue* q)
{
	assert(q);
	while (q->head)//释放所有节点
	{
		QNode* next = q->head->next;
		free(q->head);
		q->head = next;
	}
	q->head = q->tail = NULL;
	q->size = 0;
}

🌾入队列(队尾插入)

  • 接收两个参数,队列的地址和要插入的数据
  • 首先,对形参指针判空
  • 然后申请新节点,next指针指向NULL,数据部分为要插入的数据
  • 接下来,对空队列和非空队列分别处理:
  • 空队列直接让首尾指针都指向新节点
  • 非空队列:尾指针指向节点的next指针指向新节点,尾指针再指向新节点
  • 完成插入之后,size++
// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)//判定是否申请成功
	{
		perror("newnode error\n");
		exit(1);
	}
	newnode->data = data;
	newnode->next = NULL;

	if (q->head == NULL)//对空队列入队的处理
	{
		q->head = q->tail = newnode;
	}
	else               //对非空队列入队的处理
	{
		q->tail->next = newnode;
		q->tail = newnode;
	}
	q->size++;
}

🌾出队列(队头删除)

  • 对形参判空
  • 对队列判空
  • 队首删除分两种情况
  • 队列只有一个元素的情况:    释放该元素空间,首尾指针都指向NULL
  • 队列有多个元素的情况:    记录下第二个节点地址,释放首节点,首指针指向第二个节点
  • 删除完节点之后,size--
// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->head);//队列不能为空
	if (q->head == q->tail)//对只有一个元素的队列的出队处理
	{
		free(q->head);
		q->head = q->tail = NULL;
	}
	else          //对存在多个元素的队列的出队处理
	{
		QNode* next = q->head->next;
		free(q->head);
		q->head = next;
	}
	q->size--;
}

🌾获取队首元素

  • 形参判空
  • 队列判空
  • 返回队首元素的数据
// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->head);//队列不能为空
	return q->head->data;
}

🌾获取队尾元素

  • 形参判空
  • 队列判空
  • 返回队尾元素的数据
// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->head);
	return q->tail->data;
}

🌾获取队列元素个数

  • 形参判空
  • 返回size
// 获取队列中有效元素个数 
int QueueSize(Queue* q)
{
	assert(q);
	return q->size;
}

🌾队列判空

  • 形参判空
  • 返回size==0的结果
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q)
{
	assert(q);
	return q->size == 0;
}

五、C语言实现代码

Queue.h   队列头文件

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

// 链式结构:表示队列 
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* head;
	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);

Queue.c    队列实现文件

#include"Queue.h"

// 初始化队列 
void QueueInit(Queue* q)
{
	assert(q);//接收的地址必须是有效的(队列必须存在)
	q->head = q->tail = NULL;
	q->size = 0;
}

// 队尾入队列 
void QueuePush(Queue* q, QDataType data)
{
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)//判定是否申请成功
	{
		perror("newnode error\n");
		exit(1);
	}
	newnode->data = data;
	newnode->next = NULL;

	if (q->head == NULL)//对空队列入队的处理
	{
		q->head = q->tail = newnode;
	}
	else               //对非空队列入队的处理
	{
		q->tail->next = newnode;
		q->tail = newnode;
	}
	q->size++;
}

// 队头出队列 
void QueuePop(Queue* q)
{
	assert(q);
	assert(q->head);//队列不能为空
	if (q->head == q->tail)//对只有一个元素的队列的出队处理
	{
		free(q->head);
		q->head = q->tail = NULL;
	}
	else          //对存在多个元素的队列的出队处理
	{
		QNode* next = q->head->next;
		free(q->head);
		q->head = next;
	}
	q->size--;
}

// 获取队列头部元素 
QDataType QueueFront(Queue* q)
{
	assert(q);
	assert(q->head);//队列不能为空
	return q->head->data;
}

// 获取队列队尾元素 
QDataType QueueBack(Queue* q)
{
	assert(q);
	assert(q->head);
	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);
	while (q->head)//释放所有节点
	{
		QNode* next = q->head->next;
		free(q->head);
		q->head = next;
	}
	q->head = q->tail = NULL;
	q->size = 0;
}

test.c        main函数测试文件

#include"Queue.h"

void test1()
{
	Queue Q;
	QueueInit(&Q);//创建队列并初始化
	if (QueueEmpty(&Q))
		printf("队列空\n");
	else
		printf("队列非空\n");

	QueuePush(&Q, 1);//队列插入四个数据
	QueuePush(&Q, 2);
	QueuePush(&Q, 3);
	QueuePush(&Q, 4);
	if (QueueEmpty(&Q))
		printf("队列空\n");
	else
		printf("队列非空\n");
	printf("%d\n", QueueBack(&Q));//取队尾数据

	while (Q.size)//队列不为空时,每次取队首数据,再出队列
	{
		printf("%d ", QueueFront(&Q));
		QueuePop(&Q);
	}
	printf("\n");
	if (QueueEmpty(&Q))
		printf("队列空\n");
	else
		printf("队列非空\n");
	QueueDestroy(&Q);//队列销毁
}

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

测试结果

六、应用场景

单链表队列的应用场景非常广泛,几乎在所有需要按照特定顺序处理数据的情况下都可以看到它的身影。以下是一些具体的应用场景示例:

  1. 操作系统中的任务调度:在操作系统中,任务(如进程或线程)经常需要按照某种顺序(如优先级、到达时间等)被调度执行。队列可以很好地满足这种需求,确保任务按照预定的顺序被处理。使用单链表实现的队列能够动态地添加和删除任务,非常适合这种场景。

  2. 数据包缓冲处理:在网络通信中,数据包经常需要在一个缓冲区中等待处理。队列结构能够确保数据包按照接收的顺序被处理,避免了乱序问题。单链表队列可以方便地添加新接收的数据包到队尾,并从队头取出数据包进行处理。

  3. 打印机任务队列:在打印多个文档时,打印机会按照接收文档的顺序进行打印。使用队列可以确保文档按照正确的顺序被处理。单链表队列可以动态地添加新的打印任务,并从队头取出任务进行打印。

  4. 事件驱动编程:在事件驱动编程模型中,事件(如用户输入、定时器事件等)会被放入一个事件队列中等待处理。使用队列可以确保事件按照发生的顺序被处理,避免了并发事件导致的混乱。单链表队列可以方便地添加新事件到队尾,并从队头取出事件进行处理。

  5. 游戏开发:在游戏中,经常需要处理大量的游戏对象(如玩家、怪物、子弹等)。使用队列可以确保这些对象按照特定的顺序(如创建顺序、优先级等)被更新或渲染。单链表队列可以动态地添加和删除游戏对象,提高游戏的性能和响应速度。

七、总结

通过本文的介绍,我们了解了如何使用单链表来实现队列,并探讨了其在实际应用中的重要性和应用场景。单链表队列具有动态分配内存、无需预先定义大小等优点,能够方便地添加和删除元素,满足各种按照顺序处理数据的需求。无论是在操作系统、网络通信、打印机任务处理、事件驱动编程还是游戏开发中,我们都可以看到单链表队列的身影。因此,掌握单链表队列的实现原理和应用方法对于程序员来说是非常有必要的。希望本文能够帮助读者更好地理解单链表队列的概念和应用,并在实际项目中灵活运用。

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

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

相关文章

Socket编程权威指南(四)彻底解密 Epoll 原理

在上一篇文章中&#xff0c;我们优化了基于 Socket 的网络服务器&#xff0c;从最初的 select/poll 模型进化到了高效的 epoll。很多读者对 epoll 的惊人性能表示极大的兴趣&#xff0c;对它的工作原理也充满了好奇。今天&#xff0c;就让我们一起揭开 epoll 神秘的面纱&#x…

DeepSORT(目标跟踪算法)中的马氏距离详解(很详细)

DeepSORT&#xff08;目标跟踪算法&#xff09;中的马氏距离详解&#xff08;很详细&#xff09; flyfish 马氏距离的公式是由印度统计学家【普拉萨纳钱德拉马哈拉诺比斯&#xff08;Prasanta Chandra Mahalanobis&#xff09;】&#xff09;&#xff08;好长的名字&#xff…

挑战绝对不可能:再证有长度不同的射线

黄小宁 一空间坐标系中有公共汽车A&#xff0c;A中各座位到司机处的距离h是随着座位的不同而不同的变数&#xff0c;例如5号座位到司机处的距离是h3&#xff0c;…h5&#xff0c;…。A移动了一段距离变为汽车B≌A&#xff0c;B中5号座位到司机处的距离h’h3&#xff0c;…h’h5…

Redis原理篇——哨兵机制

Redis原理篇——哨兵机制 1.Redis哨兵2.哨兵工作原理2.1.哨兵作用2.2.状态监控2.3.选举leader2.4.failover 1.Redis哨兵 主从结构中master节点的作用非常重要&#xff0c;一旦故障就会导致集群不可用。那么有什么办法能保证主从集群的高可用性呢&#xff1f; 2.哨兵工作原理 …

Leetcode 力扣114. 二叉树展开为链表 (抖音号:708231408)

给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1&#xf…

力扣每日一题 6/10

881.救生艇[中等] 题目&#xff1a; 给定数组 people 。people[i]表示第 i 个人的体重 &#xff0c;船的数量不限&#xff0c;每艘船可以承载的最大重量为 limit。 每艘船最多可同时载两人&#xff0c;但条件是这些人的重量之和最多为 limit。 返回 承载所有人所需的最小船…

Qt实现简易播放器

效果如图 源码地址&#xff1a; 简易播放器: 基于Qt的简易播放器&#xff0c;底层采用VLC源码 - Gitee.com GitHub:GitHub - a-mo-xi-wei/easy-player: 基于Qt的调用VLC的API的简易播放器

蓝牙安全入门——两道CTF题目复现

文章目录 蓝牙安全入门题目 low_energy_crypto获取私钥解密 题目 蓝牙钥匙的春天配对过程配对方法密钥分发数据加密安全漏洞和保护实际应用实际应用 蓝牙安全入门 &#x1f680;&#x1f680;最近一直对车联网比较感兴趣&#xff0c;但是面试官说我有些技术栈缺失&#xff0c;所…

【Linux】进程7——进程地址空间

1.再谈fork 之前提到了fork之后对父子进程返回不同的id值&#xff0c;给父进程返回子进程的pid&#xff0c;给子进程返回0&#xff0c;所以对于一个id如何存储两个值的说法&#xff0c;在我们之前已经提到过了一个概念叫做写时拷贝&#xff0c;就是在子进程要想修改父进程的id…

AI 边缘计算平台 - 回归开源 BeagleY-AI 简介

BeagleBoard.org 于 3 月 27 号发布了一款单板计算机 BeagleY-AI &#xff0c;这款 SBC 凭借其完全开源的特性&#xff0c;旨在激发并推动开源社区的生态系统繁荣发展。 一、简介&#xff1a; BeagleY-AI 采用德州仪器新推出的 AM67A AI 视觉处理器。这款处理器集成了四个 64…

招聘在家抄书员?小心是骗局!!!

在家抄书员的骗局是一种常见的网络诈骗手段&#xff0c;旨在利用人们想要在家轻松赚钱的心理。这种骗局通常会以招聘兼职抄写员的形式出现&#xff0c;声称只需在家中抄写书籍即可赚取可观的收入。然而&#xff0c;实际上这背后隐藏着诸多陷阱和虚假承诺。 首先&#xff0c;这些…

程序猿大战Python——流程控制——while循环

程序里的循环 目标&#xff1a;了解循环语句的作用。 在程序中&#xff0c;有时候会遇到代码需要重复多次运行的情况。 例如&#xff0c;一起来完成&#xff1a; &#xff08;1&#xff09;在生活中做事没让媳妇儿满意&#xff0c;跟她承认错误&#xff0c;说10遍&#xff1a…

Java加密体系结构参考指南-Java Cryptography Architecture

本文是从英文的官网摘了翻译的&#xff0c;用作自己的整理和记录。水平有限&#xff0c;欢迎指正。版本是&#xff1a;22 原文地址&#xff1a;https://docs.oracle.com/en/java/javase/22/security/java-cryptography-architecture-jca-reference-guide.html#GUID-815542FE-CF…

Vue 2看这篇就够了

Vue 2 技术文档 Vue.js 是一款用于构建用户界面的渐进式框架。与其他重量级框架不同的是&#xff0c;Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或既有项目整合。而 Vue.js 2&#xff08;以下简称 Vue…

Java——IO流(一)-(2/9):File类的常用方法(判断文件类型、获取文件信息、创建删除文件、遍历文件夹)

目录 常用方法1&#xff1a;判断文件类型、获取文件信息 方法 实例演示 常用方法2&#xff1a;创建文件、删除文件 方法 实例演示 常用方法3&#xff1a;遍历文件夹 方法 实例演示 常用方法1&#xff1a;判断文件类型、获取文件信息 方法 File提供的判断文件类型、获…

转型AI产品经理(6):“ 序列位置效应”如何应用在Chatbot产品中

序列位置效应是心理学中的一个记忆现象&#xff0c;指的是人们对一系列信息的记忆效果受到信息在序列中位置的影响。具体来说&#xff0c;人们通常更容易记住列表的开头和结尾部分的项目&#xff0c;而对中间部分的项目记忆较差。这个效应可以进一步分为“首因效应”和“近因效…

32、matlab:基于模板匹配的车牌识别

1、准备工作 1&#xff09;准备材料 车牌字符模板和测试的实验车牌 2&#xff09;车牌字符模板 数字、字母和省份缩写 3&#xff09;测试车牌 四张测试车牌 2、车牌识别实现(已将其嵌入matlab) 1&#xff09;打开APP 找到APP 找到我的APP双击点开 2)界面介绍 包括&am…

最新下载:Navicat for MySQL 11软件安装视频教程

软件简介&#xff1a; Navicat for MySQL 是一款强大的 MySQL 数据库管理和开发工具&#xff0c;它为专业开发者提供了一套强大的足够尖端的工具&#xff0c;但对于新用户仍然易于学习。Navicat For Mysql中文网站&#xff1a;http://www.formysql.com/ Navicat for MySQL 基于…

html+CSS+js部分基础运用19

1. 应用动态props传递数据&#xff0c;输出影片的图片、名称和描述等信息【要求使用props】&#xff0c;效果图如下&#xff1a; 2.在页面中定义一个按钮和一行文本&#xff0c;通过单击按钮实现放大文本的功能。【要求使用$emit()】 代码可以截图或者复制黏贴放置在“实验…

Nginx之初识

1.Nginx概述 Nginx是一个高性能的反向代理和Web服务器软件&#xff0c;因其系统资源消耗低、运行稳定且具有高性能的并发处理能力等特性&#xff0c;在互联网企业中得到广泛的应用。 2.Nginx特性 1.访问路由 现今大型网站的请求量早已不是单一Web服务器可以支撑的了。单一入口…