数据结构队列详解:从概念到代码实现

个人专栏:《数据结构-初阶》《经典OJ题目》《C语言》

欢迎各位大佬交流!

目录

一、队列的概念及结构

1、队列的基本概念

2、队列的结构

二、代码实现

0、初始化

1、入队

2、出队

3、获取队头元素

4、获取队尾元素

5、获取队列中有效元素个数

6、检测队列是否为空

7、销毁队列

三、测试代码

[1、测试入队 + 出队 + 判空](#1、测试入队 + 出队 + 判空)

2、测试获取队头元素

3、测试获取队尾元素

一、队列的概念及结构

1、队列的基本概念

队列是一种线性数据结构,遵循**先进先出(FIFO, First In First Out)**原则;

最早进入队列的元素最先被移除,类似于现实生活中的排队场景;

队列的操作通常限制在两端进行:元素从队尾(rear)入队,从队头(front)出队

2、队列的结构

队列的存储结构既可以用数组来实现,也可以用链表来实现;

注意:此处我们说的链表是单链表!不考虑双向链表是因为双向链表空间较大,且双向链表功能复杂,性能较低!

那么到底是用数组来实现栈?还是链表呢?

同样地,我们和分析栈的实现方式一样,通过一些操作的时间复杂度来来进行分析:

a、对于入队而言:如果是链表实现的队列,首先需要找到队尾元素,这样就需要O(N)级别的时间复杂度;

如果是数组实现的队列,我们可以判断完空间情况后直接利用 top 尾插;属于O(1)级别

b、对于出队而言;如果是链表实现的队列,更改释放完头节点后更改头节点即可;O(1)级别

如果是数组实现的队列,想要出队就需要从第二个节点开始均向前挪动一位,属于O(N)级别

显然:我们不想频繁地移动数据;

但是如果使用链表的话,入队又是O(N)级别的;该怎么优化呢?

为什么入队是O(N)级别的?是因为每次都需要遍历一遍队列才能找到最后一个节点,那不妨我们在设计参数时带上尾指针;

这样不就将O(N)降低到了O(1)级别吗?

这样的话我们在入队时传入的参数就有三个,分别是头指针,尾指针,变量x;

并且传入的还得是二级指针;这样就使得函数看起来很冗余!

能不能再优化一下?

如果我们定义一个结构体包含头尾指针会怎样?

这样每次传入只需要传入这个结构体的地址即可,并且用一级指针来接收完全可行;

这样就使得函数显得简洁、高效

综上:我们选择用链表来实现队列

二、代码实现

注意:我们是用链表来实现栈

说明:由于队列的实现较为简单,因此所有函数都封装好之后再一起测试

首先来完成准备工作:同样创建三个文件,Queue.c、Queue .h、test.c

接着定义出Queue结构体

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int QDatatype;

//定义Queue
typedef struct QListNode
{
	struct QListNode* next;
	QDatatype val;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
}Queue;

0、初始化

分析逻辑:

将Queue结构体中phead 和 ptail 置为空

cpp 复制代码
//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	//在获取队列中有效元素个数中说明size
	pq->size = 0;
}

1、入队

分析逻辑:

首先申请节点空间,记得将 val 置为 x,next 置为空;

接着判断是否是入队的第一个元素;

如果是,就要修改头尾指针,否则就直接像链表尾插操作一样;

cpp 复制代码
//入队
void QueuePush(Queue* pq, QDatatype x)
{
	assert(pq);
	assert(pq->phead);
	//申请节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{	
		perror("malloc failed!\n");
		return;
	}
	newnode->val = x;
	newnode->next = NULL;

	if (pq->ptail == NULL)
	{
		//第一个入队元素
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		//直接在队尾插入
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	//在获取队列中有效元素个数中说明size
	pq->size++;
}

2、出队

分析逻辑:

注意:出队是从头节点那一端出队

由链表中头删的经验可知无论是一个元素还是多个元素,删除逻辑都是一样的!

因此我们先暂存头节点的下一个节点next;

接着 free 头节点;

最终将 next 赋值给 头节点;

最后一个元素出队后,将尾指针置为空!

cpp 复制代码
//出队
void QueuePop(Queue* pq)
{
	assert(pq);
	//队列不能为空
	assert(pq->size > 0);
	QNode* next = pq->phead->next;
	free(pq->phead);
	pq->phead = next;
	if (pq->phead == NULL)
	{
		pq->ptail = NULL;
	}
	//在获取队列中有效元素个数中说明size
	pq->size--;
}

3、获取队头元素

分析逻辑:

直接返回头节点的 val 即可

cpp 复制代码
//获取队头元素
QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);
	return pq->phead->val;
}

4、获取队尾元素

分析逻辑:

直接返回尾节点的 val 即可

cpp 复制代码
//获取队尾元素
QDatatype QueueBAck(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);
	return pq->ptail->val;
}

5、获取队列中有效元素个数

分析逻辑:

注意:不能直接用尾指针 - 头指针!两者并非顺序存储!

那难道还要遍历一遍队列吗?

我们直接在定义头尾指针的结构体中加入 size 变量;

用 size 统计元素个数

此时我们就需要更新我们上面的代码,更新 pq->size ;

cpp 复制代码
//获取队列中有效元素个数
int QueueSize(Queue* pq)
{
	//不能直接用尾指针 - 头指针
	//两个指针并非顺序存储
	assert(pq);
	return pq->size;
}

6、检测队列是否为空

分析逻辑:

其实就是判断头指针或尾指针是否等于NULL

cpp 复制代码
//检测队列是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	//return pq->phead == NULL;
	//在获取队列中有效元素个数中说明size
	return pq->size == 0;
}

7、销毁队列

分析逻辑:

按照顺序从头到尾遍历一遍,按顺序销毁即可!

最终在执行完函数之后记得显式将结构体指针置为空!

因为传入的是一级结构体指针,无法修改指针本身

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

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

三、测试代码

1、测试入队 + 出队 + 判空

2、测试获取队头元素

3、测试获取队尾元素

如有不足之处恳请指出!!!

相关推荐
Resistance丶未来1 小时前
DeepSeek-V4 新手快速上手指南
数据结构·python·gpt·算法·机器学习·claude·claude 4.6
光子物联单片机1 小时前
STM32传感器模块编程实践(二十)ESP8266实现MQTT连接OneNET上传温湿度数据
c语言·stm32·单片机·嵌入式硬件·mqtt
人道领域2 小时前
【LeetCode刷题日记】150.逆波兰表达式求值
java·数据结构·算法·leetcode
此生决int2 小时前
快速复习之数据结构篇——顺序表
数据结构
RH2312112 小时前
2026.4.26数据结构 链地址法
数据结构
凯瑟琳.奥古斯特2 小时前
常见排序算法性能对比
数据结构·算法·排序算法
流年如夢2 小时前
编译前奏:预处理全面梳理
c语言
freshman_y13 小时前
一篇介绍C语言中二级指针和二维数组的文章
c语言·开发语言
weixin_4139206114 小时前
LVGL仪表显示项目
c语言