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

目录

一、概念与结构

二、队列的实现

1、队列的初始化

[1. 函数目的](#1. 函数目的)

[2. 参数说明](#2. 参数说明)

[3. 断言检查](#3. 断言检查)

[4. 初始化队列的头尾指针](#4. 初始化队列的头尾指针)

[5. 初始化队列的大小](#5. 初始化队列的大小)

[6. 总结](#6. 总结)

2、入队列队尾

[1. 函数目的](#1. 函数目的)

[2. 参数说明](#2. 参数说明)

[3. 断言检查](#3. 断言检查)

[4. 申请新节点](#4. 申请新节点)

[5. 初始化新节点](#5. 初始化新节点)

[6. 将新节点插入队列](#6. 将新节点插入队列)

[7. 更新队列大小](#7. 更新队列大小)

[8. 总结](#8. 总结)

3、队列判空

[1. 函数目的](#1. 函数目的)

[2. 参数说明](#2. 参数说明)

[3. 断言检查](#3. 断言检查)

[4. 判断队列是否为空](#4. 判断队列是否为空)

[5. 总结](#5. 总结)

4、出队列队头

[1. 参数检查](#1. 参数检查)

[2. 处理队列中只有一个节点的情况](#2. 处理队列中只有一个节点的情况)

[3. 处理队列中有多个节点的情况](#3. 处理队列中有多个节点的情况)

[4. 更新队列大小](#4. 更新队列大小)

[5. 总结](#5. 总结)

5、取队头数据

[1. 断言检查](#1. 断言检查)

[2. 返回队头数据](#2. 返回队头数据)

[3. 总结](#3. 总结)

6、取队尾数据

[1. 断言检查](#1. 断言检查)

[2. 返回队尾数据](#2. 返回队尾数据)

7、队列有效元素个数

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

[2. 直接返回 pq->size 的部分](#2. 直接返回 pq->size 的部分)

[3. 两种实现方式的比较](#3. 两种实现方式的比较)

[4. 总结](#4. 总结)

8、销毁队列

[1. 函数声明](#1. 函数声明)

[2. 参数检查](#2. 参数检查)

[3. 遍历队列并释放节点](#3. 遍历队列并释放节点)

[4. 重置队列状态](#4. 重置队列状态)

[5. 总结](#5. 总结)

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

Queue.h

Queue.c

test.c


一、概念与结构

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

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

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

队列底层结构选型:

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

二、队列的实现

1、队列的初始化

cpp 复制代码
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。如果 pqNULL,程序会在这里终止并报错。这是一种防御性编程的手段,确保传入的指针是有效的。

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

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

5. 初始化队列的大小

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

6. 总结

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

2、入队列队尾

cpp 复制代码
// ⼊队列,队尾
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。如果 pqNULL,程序会终止并报错。这是一种防御性编程的手段,确保传入的指针是有效的。

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):如果队列的头指针 pheadNULL,说明队列当前为空。

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

  • 情况 2:队列不为空

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

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


7. 更新队列大小

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

8. 总结

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

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

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

3、队列判空

cpp 复制代码
//队列判空
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。如果 pqNULL,程序会终止并报错。这是一种防御性编程的手段,确保传入的指针是有效的。

4. 判断队列是否为空

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

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

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

    • 否则,返回 false


5. 总结

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

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

4、出队列队头

cpp 复制代码
// 出队列,队头
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、取队头数据

cpp 复制代码
//取队头数据
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、取队尾数据

cpp 复制代码
//取队尾数据
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、队列有效元素个数

cpp 复制代码
//队列有效元素个数
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、销毁队列

cpp 复制代码
//销毁队列
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

cpp 复制代码
#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

cpp 复制代码
#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

cpp 复制代码
#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;
}
相关推荐
我命由我1234542 分钟前
游戏引擎 Unity - Unity 打开项目、Unity Editor 添加简体中文语言包模块、Unity 项目设置为简体中文
c语言·开发语言·c++·unity·ue5·c#·游戏引擎
MiyamiKK572 小时前
leetcode_双指针 160.相交链表
算法·leetcode·链表
迪小莫学AI2 小时前
LeetCode 2909. 元素和最小的山形三元组 II
算法·leetcode·职场和发展
鸿业远图科技2 小时前
网件r7000刷回原厂固件合集测评
网络·智能路由器
EasyGBS2 小时前
设备通过国标GB28181接入EasyCVR,显示在线但视频无法播放的原因排查
网络·ffmpeg·音视频
网络安全Jack2 小时前
网络安全—DDoS攻防
网络·web安全·ddos
Lenyiin3 小时前
2848、与车相交的点
c++·算法·leetcode·1024程序员节
mljy.3 小时前
优选算法《前缀和》
c++·算法
小仇学长3 小时前
嵌入式八股文面试题(一)C语言部分
c语言·c++·面试·嵌入式·八股文