C_数据结构(队列) —— 队列的初始化、入队列队尾、队列判空、出队列队头、取队头队尾数据、队列有效元素个数、销毁队列

news/2025/2/5 22:29:48 标签: 网络, c语言, 数据结构, 排序算法, 算法

目录

一、概念与结构

二、队列的实现

1、队列的初始化

1. 函数目的

2. 参数说明

3. 断言检查

4. 初始化队列的头尾指针

5. 初始化队列的大小

6. 总结

2、入队列队尾

1. 函数目的

2. 参数说明

3. 断言检查

4. 申请新节点

5. 初始化新节点

6. 将新节点插入队列

7. 更新队列大小

8. 总结

3、队列判空

1. 函数目的

2. 参数说明

3. 断言检查

4. 判断队列是否为空

5. 总结

4、出队列队头

1. 参数检查

2. 处理队列中只有一个节点的情况

3. 处理队列中有多个节点的情况

4. 更新队列大小

5. 总结

5、取队头数据

1. 断言检查

2. 返回队头数据

3. 总结

6、取队尾数据

1. 断言检查

2. 返回队尾数据

7、队列有效元素个数

1. 注释掉的部分 (迭代计数)

2. 直接返回 pq->size 的部分

3. 两种实现方式的比较

4. 总结

8、销毁队列

1. 函数声明

2. 参数检查

3. 遍历队列并释放节点

4. 重置队列状态

5. 总结

三、完整实现队列的三个文件

Queue.h

Queue.c 

test.c


一、概念与结构

概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出大的结构特征FIFO(First In Frist Out)

入队列:进⾏插⼊操作的⼀端称为队尾

出队列:进⾏删除操作的⼀端称为队头

队列底层结构选型:

        队列也可以数组和链表的结构实现,使⽤链表的结构实现更优⼀些,因为如果使⽤数组的结构,出队列在数组头上出数据,效率会⽐较低。

二、队列的实现

1、队列的初始化

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

1. 函数目的

  • QueueInit 函数的目的是初始化一个队列,将其设置为空队列状态。

2. 参数说明

  • Queue* pq:这是一个指向 Queue 结构体的指针,表示要初始化的队列。

3. 断言检查

  • assert(pq);:使用 assert 宏来确保传入的指针 pq 不是 NULL。如果 pq 是 NULL,程序会在这里终止并报错。这是一种防御性编程的手段,确保传入的指针是有效的。

4. 初始化队列的头尾指针

  • pq->phead = pq->ptail = NULL;:将队列的头指针 phead 和尾指针 ptail 都设置为 NULL,表示队列中没有任何元素。

5. 初始化队列的大小

  • pq->size = 0;:将队列的大小 size 设置为 0,表示队列当前为空。

6. 总结

  • 通过这个函数,队列被初始化为一个空队列,头尾指针都指向 NULL,且队列的大小为 0。这个函数通常在创建队列后立即调用,以确保队列处于一个有效的初始状态。

2、入队列队尾

// ⼊队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//申请新节点
	QueueNode* newnode =(QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//ptail newnode
	if (pq->phead == NULL)
	{//队列为空
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		//队列不为空
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;//newnode
	}
	pq->size++;
}

1. 函数目的

  • QueuePush 函数的目的是将一个元素 x 添加到队列的尾部(队尾),并更新队列的状态。


2. 参数说明

  • Queue* pq:指向队列的指针,表示要操作的队列。

  • QDataType x:要入队的元素,类型为 QDataType(可能是 intfloat 或其他自定义类型)。


3. 断言检查

  • assert(pq);:使用 assert 宏检查传入的队列指针 pq 是否为 NULL。如果 pq 是 NULL,程序会终止并报错。这是一种防御性编程的手段,确保传入的指针是有效的。


4. 申请新节点

  • QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));:动态分配内存,创建一个新的队列节点 newnode

  • if (newnode == NULL):检查内存分配是否成功。如果 malloc 失败(返回 NULL),则输出错误信息 "malloc fail!" 并调用 exit(1) 终止程序。


5. 初始化新节点

  • newnode->data = x;:将新节点的数据域 data 设置为传入的值 x

  • newnode->next = NULL;:将新节点的 next 指针设置为 NULL,表示它是队列的最后一个节点。


6. 将新节点插入队列

  • 情况 1:队列为空

    • if (pq->phead == NULL):如果队列的头指针 phead 为 NULL,说明队列当前为空。

    • pq->phead = pq->ptail = newnode;:将队列的头指针 phead 和尾指针 ptail 都指向新节点 newnode,因为新节点是队列中唯一的节点。

  • 情况 2:队列不为空

    • pq->ptail->next = newnode;:将当前尾节点 ptail 的 next 指针指向新节点 newnode,将新节点链接到队列的尾部。

    • pq->ptail = pq->ptail->next;:更新尾指针 ptail,使其指向新节点 newnode,因为新节点现在是队列的最后一个节点。


7. 更新队列大小

  • pq->size++;:将队列的大小 size 加 1,表示队列中新增了一个元素。


8. 总结

  • 这个函数的核心逻辑是将新节点插入到队列的尾部,并更新队列的头尾指针和大小。

  • 如果队列为空,新节点既是头节点也是尾节点。

  • 如果队列不为空,新节点被链接到当前尾节点的后面,并成为新的尾节点。

3、队列判空

//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL && pq->ptail == NULL;
}

1. 函数目的

  • QueueEmpty 函数的目的是判断队列是否为空。如果队列为空,返回 true;否则返回 false


2. 参数说明

  • Queue* pq:指向队列的指针,表示要检查的队列。


3. 断言检查

  • assert(pq);:使用 assert 宏检查传入的队列指针 pq 是否为 NULL。如果 pq 是 NULL,程序会终止并报错。这是一种防御性编程的手段,确保传入的指针是有效的。


4. 判断队列是否为空

  • return pq->phead == NULL && pq->ptail == NULL;

    • 通过检查队列的头指针 phead 和尾指针 ptail 是否都为 NULL 来判断队列是否为空。

    • 如果 phead 和 ptail 都为 NULL,说明队列中没有元素,返回 true

    • 否则,返回 false


5. 总结

  • 这个函数的逻辑非常简单,直接通过检查队列的头尾指针是否都为 NULL 来判断队列是否为空。

  • 这是一种高效且直观的判空方法,时间复杂度为 O(1)。

4、出队列队头

// 出队列,队头
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	//只有一个结点的情况,避免ptail变成野指针
	if (pq->ptail == pq->phead)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		//删除队头元素、
		QueueNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	--pq->size;
}

1. 参数检查

  • assert(pq);:确保传入的队列指针 pq 不是空指针。如果 pq 为空,程序会终止并报错。

  • assert(!QueueEmpty(pq));:确保队列不为空。如果队列为空,无法执行出队操作,程序会终止并报错。

2. 处理队列中只有一个节点的情况

  • if (pq->ptail == pq->phead):检查队列中是否只有一个节点。如果队头 (phead) 和队尾 (ptail) 指向同一个节点,说明队列中只有一个元素。

  • free(pq->phead);:释放这个唯一的节点。

  • pq->phead = pq->ptail = NULL;:将队头和队尾指针都置为 NULL,表示队列为空。

3. 处理队列中有多个节点的情况

  • else:如果队列中有多个节点,执行以下操作:

    • QueueNode* next = pq->phead->next;:保存队头节点的下一个节点(即新的队头)。

    • free(pq->phead);:释放当前的队头节点。

    • pq->phead = next;:将队头指针指向新的队头节点。

4. 更新队列大小

  • --pq->size;:减少队列的大小(size),表示队列中元素的数量减少了一个。

5. 总结

        这段代码的核心逻辑是删除队列的队头元素,并处理队列中只有一个节点和多个节点的不同情况。通过释放队头节点的内存,并更新队头指针和队列大小,确保队列的状态正确。

5、取队头数据

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

1. 断言检查

  • assert(pq); 和 assert(!QueueEmpty(pq)); 首先进行断言检查,确保队列指针 pq 有效(非空)且队列不为空。 这有助于在开发阶段发现潜在的错误,提高代码的健壮性。

2. 返回队头数据

  • return pq->phead->data; 如果断言检查通过,则直接返回队列头结点 (pq->phead) 的数据成员 (data)。 因为 phead 指针指向队列的第一个元素,所以 pq->phead->data 就代表了队列头部的元素值。 

3. 总结

  • 这个函数的功能非常简单直接:获取队列头部的元素值。 它依赖于队列的内部实现,假设队列已经正确构建,并且 phead 指针指向队列的第一个元素。 断言的使用增强了函数的可靠性,防止了对空指针或空队列的访问。

6、取队尾数据

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}

1. 断言检查

  • assert(pq); 和 assert(!QueueEmpty(pq)); 首先进行断言检查,确保队列指针 pq 有效(非空)且队列不为空。 这防止了对空指针或空队列的访问,提高了代码的健壮性。

2. 返回队尾数据

  • return pq->ptail->data; 如果断言检查通过,则直接返回队列尾部节点 (pq->ptail) 的数据成员 (data)。 因为 ptail 指针始终指向队列的最后一个元素,所以 pq->ptail->data 直接获取了队列尾部的元素值。

3. 总结

  • 函数 QueueBack 的功能是获取队列的最后一个元素的值。 它简洁明了,直接通过 ptail 指针访问队尾元素的数据。 断言的加入,增强了代码的可靠性,避免了潜在的错误。 该函数依赖于队列的内部实现,假设队列已经正确构建,并且 ptail 指针正确地指向队列的尾部节点。

7、队列有效元素个数

//队列有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	/*int size = 0;
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		size++ ;
		pcur = pcur->next;
	}
	return size;*/
	return pq->size;
}

1. 注释掉的部分 (迭代计数)

  1. 断言检查: assert(pq); 首先检查队列指针 pq 是否有效(非空)。

  2. 初始化: int size = 0; 初始化计数器 size 为 0,用于统计队列元素个数。

  3. 遍历队列: QueueNode* pcur = pq->phead; 创建一个指针 pcur,指向队列的头结点 (pq->phead)。 while (pcur)循环遍历队列中的所有节点,直到 pcur 为空(到达队列尾部)。

  4. 计数: 在循环体中,size++; 每遍历一个节点,计数器 size 加 1。

  5. 返回计数: return size; 循环结束后,size 中保存的就是队列中元素的个数,函数返回这个值。

2. 直接返回 pq->size 的部分

        这段代码直接返回队列结构体中的 size 成员变量。 这假设队列结构体中已经维护了一个 size 变量,用于实时跟踪队列中元素的个数。 每次进行入队或出队操作时,都会更新这个 size 变量。

3. 两种实现方式的比较

  • 迭代计数: 这种方式每次调用 QueueSize 函数都需要遍历整个队列,时间复杂度为 O(n),其中 n 为队列中元素个数。 它不需要维护额外的 size 变量,代码相对简单。

  • 直接返回 pq->size: 这种方式的时间复杂度为 O(1),效率更高。 但是,需要在队列的入队和出队操作中额外维护 size 变量,增加了代码的复杂度。

4. 总结

        注释掉的部分是通过遍历队列来计算大小,时间复杂度较高;而 return pq->size; 是通过维护一个计数器来直接返回大小,时间复杂度为 O(1),效率更高。 哪种方式更好取决于具体的队列实现和性能要求。 如果性能是关键因素,那么维护一个 size 变量是更优的选择。 如果简洁性更重要,或者队列的实现不允许维护 size 变量,那么迭代计数的方法也是可行的。

8、销毁队列

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

1. 函数声明

  • 函数名为 QueueDestroy,返回类型为 void,表示该函数不返回任何值。

  • 参数 Queue* pq 是一个指向队列的指针,用于操作队列。


2. 参数检查

  • assert(pq);:使用 assert 宏检查传入的队列指针 pq 是否为空。如果 pq 为空,程序会终止并报错。这一步是为了防止空指针导致的程序崩溃。

  • assert(!QueueEmpty(pq));:使用 assert 宏检查队列是否为空。如果队列为空(即 QueueEmpty(pq) 返回 true),程序会终止并报错。这一步是为了确保队列中有数据可以销毁。


3. 遍历队列并释放节点

  • QueueNode* pcur = pq->phead;:定义一个指针 pcur,初始化为指向队列的头节点(pq->phead)。

  • while (pcur):使用 while 循环遍历队列中的每一个节点,直到 pcur 为空(即遍历完所有节点)。

    • QueueNode* next = pcur->next;:在释放当前节点之前,先保存当前节点的下一个节点指针 next,以便后续继续遍历。

    • free(pcur);:释放当前节点 pcur 的内存。

    • pcur = next;:将 pcur 指向下一个节点,继续遍历。


4. 重置队列状态

  • pq->phead = pq->ptail = NULL;:将队列的头指针 phead 和尾指针 ptail 都设置为 NULL,表示队列为空。

  • pq->size = 0;:将队列的大小 size 设置为 0,表示队列中没有元素。


5. 总结

  • 该函数的核心逻辑是遍历队列中的所有节点,逐个释放节点的内存,最后将队列重置为空状态。

  • 通过 assert 检查确保了队列指针的有效性和队列非空,避免了潜在的错误。

三、完整实现队列的三个文件

Queue.h

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

//定义队列结构
typedef int QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QueueNode;

typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;//保存队列有效数据个数
}Queue;

void QueueInit(Queue* pq);
// ⼊队列,队尾
void QueuePush(Queue* pq, QDataType x);
// 出队列,队头
void QueuePop(Queue* pq);

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

//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列有效元素个数
int QueueSize(Queue* pq);

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

Queue.c 

#include"Queue.h"
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
// ⼊队列,队尾
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//申请新节点
	QueueNode* newnode =(QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//ptail newnode
	if (pq->phead == NULL)
	{//队列为空
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		//队列不为空
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;//newnode
	}
	pq->size++;
}

//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->phead == NULL && pq->ptail == NULL;
}

// 出队列,队头
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	//只有一个结点的情况,避免ptail变成野指针
	if (pq->ptail == pq->phead)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
		//删除队头元素、
		QueueNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	--pq->size;
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}
//队列有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	/*int size = 0;
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		size++ ;
		pcur = pcur->next;
	}
	return size;*/
	return pq->size;
}

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

test.c

#include"Queue.h"

void QueueTest01()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);

	QueuePop(&q);
	//QueuePop(&q);
	//QueuePop(&q);
	//QueuePop(&q);
	//QueuePop(&q);

	//printf("head:%d\n", QueueFront(&q));
	//printf("tail:%d\n", QueueBack(&q));
	printf("size:%d\n", QueueSize(&q));
	QueueDestroy(&q);
}

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

http://www.niftyadmin.cn/n/5842439.html

相关文章

LabVIEW涡轮诊断系统

一、项目背景与行业痛点 涡轮机械是发电厂、航空发动机、石油化工等领域的核心动力设备&#xff0c;其运行状态直接关系到生产安全与经济效益。据统计&#xff0c;涡轮故障导致的非计划停机可造成每小时数十万元的经济损失&#xff0c;且突发故障可能引发严重安全事故。传统人…

OpenAI新商标申请曝光:AI硬件、机器人、量子计算全线布局?

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问&#xff1a;360手机已停产&#xff0c;现在和以后&#xff0c;能刷机吗&#xff1f; 答&#xff1a;360手机&#xff0c;是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…

vue3 store刷新失效场景解决方案

1. 安装 vuex-persistedstate 插件 vuex-persistedstate 是一个常用的插件&#xff0c;可以方便地将 Vuex 状态持久化到 localStorage 或 sessionStorage 中 npm install vuex-persistedstate2. 配置 Vuex Store 使用 sessionStorage // store/index.js import { createStore }…

某音小程序反编译签名加密静态分析

文章目录 1. 写在前面2. 抓包分析3. 逆向分析 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python…

Python基础-使用list和tuple

目录 list tuple 练习 小结 list Python内置的一种数据类型是列表&#xff1a;list。list是一种有序的集合&#xff0c;可以随时添加和删除其中的元素。 比如&#xff0c;列出班里所有同学的名字&#xff0c;就可以用一个list表示&#xff1a; >>> classmates …

【Java实战】高并发场景下账户金额操作的解决方案

文章目录 前言:金融系统中的并发危机一、并发问题现场还原1.1 问题代码示例1.2 并发测试暴露问题1.3 问题根源分析二、五大解决方案深度剖析2.1 synchronized同步锁2.2 ReentrantLock显式锁2.3 CAS无锁编程(Atomic原子类)2.4 数据库乐观锁2.5 分布式锁(Redis实现)三、方案…

http请求中的headers和body内容设置

1.headers 1.1 内容相关 headers {Content-Type: application/json, # 或 application/x-www-form-urlencoded, multipart/form-dataContent-Length: 1234, # 内容长度Accept: application/json, # 期望的返回格式Accept-Encoding: gzip, deflate, # 支持的压缩方式Acce…