当前位置: 首页 > article >正文

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.kler.cn/a/532362.html

相关文章:

  • 2024-我的学习成长之路
  • 【开源免费】基于Vue和SpringBoot的工作流程管理系统(附论文)
  • python-leetcode-二叉树的层序遍历
  • C语言教学第四课:控制结构
  • 【实战篇】Android安卓本地离线实现视频检测人脸
  • deepseek的两种本地使用方式
  • JS中document获取元素方法【内涵案例】
  • Paimon写入性能
  • 读写锁: ReentrantReadWriteLock
  • 【C++STL标准模板库】二、STL三大组件
  • 数据结构与算法——二分查找
  • e2studio开发RA4M2(6)----GPIO外部中断(IRQ)配置
  • 机器学习中的关键概念:通过SKlearn的MNIST实验深入理解
  • Linux+Docer 容器化部署之 Shell 语法入门篇 【Shell 替代】
  • 神经网络常见激活函数-sigmoid函数
  • deepseek接入pycharm 进行AI编程
  • 高精度乘法(高×高)
  • 438.找到字符串中所有字母异位词
  • 数据库课程设计基于Java+MySQL+JDBC+JavaSwing的停车场管理系统源代码+数据库,进出车辆登记,车位管理
  • OSCP - Other Machines - CuteNews
  • oracle: 数据操纵语言DML/批量更新
  • C++11详解(一) -- 列表初始化,右值引用和移动语义
  • leetcode 1124. 表现良好的最长时间段
  • 开发板目录 /usr/lib/fonts/ 中的字体文件 msyh.ttc 的介绍【微软雅黑(Microsoft YaHei)】
  • Linux基础 ——tmux vim 以及基本的shell语法
  • MySQL知识点总结(十八)