数据结构 --- 队列

队列 --- Queue

前言

队列是一种特殊的线性表,其特性是先进先出,只能在队尾入数据,在队头出数据,本次实现使用链表结构实现队列,对比数组实现有下列优点:

尽管现在来看两者的时间复杂度是差不多的,但是对于链表来说,我们只需要再定义一个尾节点来表示队尾即可将入队操作的时间复杂度优化为O(1),而数组的出队操作是无法优化时间复杂度的,所以综上所述本次实现选择链表结构实现队列。故而逻辑结构上是线性的,物理结构上不一定是线性的。
本次实现使用C语言实现。

一、整体结构

本次实现队列底层结构是链表,所以说有一个节点结构类型,并且队列操作数据只有队头和队尾位置可以操作,所以将其抽离出来,这就是队列的结构。

节点的结构:

c 复制代码
typedef int QDataType;
// 队列的节点结构
typedef struct QueueNode
{
	QDataType data;          // 数据域
	struct QueueNode* next;  // 指针域
}QueueNode;

队列的结构:

c 复制代码
// 队列的结构
typedef struct Queue
{
	QueueNode* phead;     // 队头
	QueueNode* ptail;     // 队尾
	// int size;          // 有效数据个数
}Queue;

二、相关方法

1.初始化

初始化即将队列phead和ptail置为空即可。

c 复制代码
// 初始化
void QueueInit(Queue* pq)
{
	// pq不能为空
	assert(pq);

	// 初始化成空
	pq->phead = pq->ptail = NULL;
	// pq->size = 0;
}

创建一个空队列q1并初始化它,

监视窗口演示:

2.销毁

常见的标记销毁位置起始的节点,并记录此位置的下一个节点,之后循环销毁节点,完成后将phead和ptail置为NULL。

c 复制代码
// 销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);

	// 标记队头
	QueueNode* pcur = pq->phead;

	// 销毁节点
	while (pcur)
	{
		// 记录pcur的下一个节点
		QueueNode* pnext = pcur->next;
		free(pcur);
		pcur = pnext;
	}

	// 销毁完成后phead和ptail置为NULL
	pq->phead = pq->ptail = NULL;
	// pq->size =0;
}

3.入队 - - - 队尾插插入数据

入队两步骤:

1)申请新节点,动态开辟(malloc)一个节点大小的内存空间,更新数据域为入队元素x,再将指针域置为NULL。

2)判断队列是否有元素,若有元素则直接在队尾ptail后面插入数据,并更新队尾指针指向;若没有元素,则新插入的节点newnode既是队头phead也是队尾ptail。

c 复制代码
// 入队 --- 队尾入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	// 申请新节点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	// 当队列为空时
	if (pq->phead == NULL)
	{
		// 新节点就是队头和队尾
		pq->phead = pq->ptail = newnode;
	}
	// 当队列不为空时
	else
	{
		// 队尾入队列
		pq->ptail->next = newnode;
		// 移动队尾指针
		pq->ptail = pq->ptail->next;
	}
	// pq->size++;
}

向初始化完成后的队列q1入队三个数据:10,20,30

监视窗口演示:

4.出队 - - - 队头删除数据

出队两步骤:

1)判断队列是否是非空状态,只有有元素才可以出队。

2)判断队列的节点个数,当只有一个节点时,销毁此节点并将phead和ptail置为NULL;当有多个节点时,也是销毁此节点,并更新队头指针。

c 复制代码
// 出队 --- 队头出
void QueuePop(Queue* pq)
{
	// 队列非空才可以出队
	assert(!QueueEmpty(pq));

	// 当队列只有一个节点
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	// 当队列有多个节点时
	else
	{
		// 标记队头的下一个节点
		QueueNode* pnext = pq->phead->next;
		free(pq->phead);
		// 移动队头至下一个节点
		pq->phead = pnext;
	}
	//pq->size--;
}

向入队完成后的队列q1出队前两个数据:10,20

监视窗口演示:

5.获取队头 / 队尾元素

获取两者的元素各自访问队头队尾指针的数据域即可,前提是队列非空。

获取队头元素:

c 复制代码
// 获取队头元素
QDataType QueueFront(Queue* pq)
{
	// 队列非空才可以获取
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

获取队尾元素:

c 复制代码
// 获取队尾元素
QDataType QueueBack(Queue* pq)
{
	// 队列非空才可以获取

	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}

6.判空

队列为空的条件是队头指针或者队尾指针等于NULL。

c 复制代码
// 判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL;
}

7.获取队列有效元素个数

有两种方法:

1)遍历队列,定义一个计数器size,每访问一个节点计数器加一。

2)在队列属性里多增加一个size属性,直接返回size即可,增加这一个属性之后,初始化,入队,出队,销毁这种需要改变size大小的方法也需要对size进行操作。

c 复制代码
// 获取队列有效元素个数
int Queuesize(Queue* pq)
{
	// 方法一:遍历,统计
	// 适用于非频繁访问有效数据个数的场景 : O(N)
	 
	// 标记队头
	QueueNode* pcur = pq->phead;
	int size = 0;
	while (pcur)
	{
		size++;
		pcur = pcur->next;
	}
	return size;

	// 方法二:给队列增加一个size属性
	// 适用于频繁访问有效数据个数的场景 :O(1)
	//return pq->size;
}
相关推荐
玖別ԅ(¯﹃¯ԅ)4 分钟前
文件追加模式:编写一个程序,向一个已存在的文件末尾追加内容。
c语言
祁思妙想19 分钟前
【LeetCode100】--- 1.两数之和【复习回滚】
数据结构·算法·leetcode
橘颂TA27 分钟前
【C++】红黑树的底层思想 and 大厂面试常问
数据结构·c++·算法·红黑树
<但凡.2 小时前
数据结构与算法之美:广义表
数据结构·c++·算法
偷偷的卷3 小时前
【算法笔记 day three】滑动窗口(其他类型)
数据结构·笔记·python·学习·算法·leetcode
C语言小火车3 小时前
野指针:C/C++内存管理的“幽灵陷阱”与系统化规避策略
c语言·c++·学习·指针
凤年徐4 小时前
【数据结构】时间复杂度和空间复杂度
c语言·数据结构·c++·笔记·算法
kualcal4 小时前
代码随想录17|二叉树的层序遍历|翻转二叉树|对称二叉树
数据结构·算法
鑫宇吖4 小时前
Polyspace作为MISRA-C合规性检查工具,其检查规则会根据目标C语言标准(C90或C99)动态调整限值要求。
c语言·嵌入式·c99·c90·polyspace·misra-c合规性检查
钮钴禄·爱因斯晨4 小时前
C语言 | 函数核心机制深度解构:从底层架构到工程化实践
c语言·开发语言·数据结构