【数据结构】队列

发布于:2025-04-03 ⋅ 阅读:(26) ⋅ 点赞:(0)


个人主页,点击这里~
数据结构专栏,点击这里~
在这里插入图片描述

一、队列

1、概念与结构

概念:只允许在⼀端进行插入数据操作,在另⼀端进⾏删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的性质。

入队列:进行插入操作的⼀端称为队尾
出队列:进行删除操作的⼀端称为队头
在这里插入图片描述
队列底层结构选择:队列可以用数组链表的结构实现,使用链表的结构实现更优⼀些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。所以我们将用链表实现队列这一数据结构。

2、队列的实现

我们要用链表实现队列,首先就要考虑先进先出这种模式下,链表该如何实现?
我们可以定义一个生成队列结点的结构体,生成一个结点看守队头,再生成一个结点看守队尾,这样我们访问队头和队尾的时候都会比较简单且节省时间,然后将这两个结点包装在一个队列结构体里面。

typedef int QDataType;
//队列结点的结构
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QN;
//队列的结构
typedef struct Queue
{
	QN* head;
	QN* tail;
}Queue;

3、队列的初始化

队列的初始化的时候,我们只需要将队列中的两个结点headtail置为空即可。

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}

4、打印队列数据

//队列打印
void QueuePrint(Queue* pq)
{
	assert(pq);
	QN* pcur = pq->head;
	while (pcur)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

5、入队

队列入队的时候是从队尾入的。在入队之前,我们要定义一个新节点newNode储存新数据,再执行入队操作。

//入队---队尾
void QueuePush(Queue* pq,QDataType x)
{
	//定义新节点
	QN* newNode = (QN*)malloc(sizeof(QN));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		return 1;
	}
	newNode->data = x;
	newNode->next = NULL;
	//入队
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}

测试代码:

#include "Queue.h"

void test()
{
	Queue plist;
	QueueInit(&plist);
	QueuePush(&plist, 1);
	QueuePush(&plist, 2);
	QueuePush(&plist, 3);
	QueuePrint(&plist);
}

int main()
{
	test();

	return 0;
}

测试结果:
在这里插入图片描述

6、销毁队列

我们在单链表博客中实现销毁链表的时候,是一个节点一个节点进行销毁的,所以我们在队列的销毁过程也一样,另外在销毁队列的时候,我们要保存要销毁节点的下一个节点,以免找不到下一个节点。

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QN* pcur = pq->head;//指向队列的队头
	while (pcur)
	{
		QN* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->head = pq->tail = NULL;
}

注意这里不用把pq也释放掉,因为pq是队列的结构体,它不是动态内存开辟出来的,只有节点才是动态内存开辟出来的,它们才需要释放。

销毁测试结果:
在这里插入图片描述
从右面可以看出所有的节点都被置为了空,队列已经完全被销毁了。

7、队列判空

我们接下来就要实现出队操作,在出队操作之前我们还需要判断队列是不是空,如果为空就不能进行出队列操作。

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

这里要用到bool类型,所以要包含头文件#include <stdbool.h>

8、出队

出队时我们要把第一个节点销毁掉

//出队---队头
void QueuePop(Queue* pq)
{
	assert(!QueueEmpty(pq));
	//队头和队尾相等,即只有一个节点
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QN* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

9、取队头、队尾数据

取队头数据:

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

取队尾数据

//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

10、队列中有效元素个数

//队列中有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	//方法一:遍历队列(适用于不频繁使用数据个数的情况)
	QN* pcur = pq->head;
	int size = 0;
	while (pcur)
	{
		size++;
		pcur = pcur->next;
	}
	return size;
	//方法二:在队列的结构体中添加变量size
	// 并在初始化时初始化为0,每当添加节点时就让它计数
	//(适用于频繁使用数据个数的情况)
	//return pq -> size;
}

测试代码

#include "Queue.h"

void test()
{
	Queue plist;
	QueueInit(&plist);
	QueuePush(&plist, 1);
	QueuePush(&plist, 2);
	QueuePush(&plist, 3);
	QueuePrint(&plist);
	printf("%d",QueueSize(&plist));
	QueueDestroy(&plist);
}

int main()
{
	test();

	return 0;
}

测试结果:
在这里插入图片描述

二、源码

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;
}QN;
//队列的结构
typedef struct Queue
{
	QN* head;
	QN* tail;
}Queue;
//队列的初始化
void QueueInit(Queue* pq);
//队列打印
void QueuePrint(Queue* pq);
//入队---队尾
void QueuePush(Queue* pq, QDataType x);
//销毁队列
void QueueDestroy(Queue* pq);
//队列判空
bool QueueEmpty(Queue* pq);
//出队---队头
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//队列中有效元素个数
int QueueSize(Queue* pq);

Queue.c

#include "Queue.h"

//队列的初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
}
//队列打印
void QueuePrint(Queue* pq)
{
	assert(pq);
	QN* pcur = pq->head;
	while (pcur)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//入队---队尾
void QueuePush(Queue* pq,QDataType x)
{
	//定义新节点
	QN* newNode = (QN*)malloc(sizeof(QN));
	if (newNode == NULL)
	{
		perror("malloc fail!");
		return 1;
	}
	newNode->data = x;
	newNode->next = NULL;
	//入队
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}
//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QN* pcur = pq->head;//指向队列的队头
	while (pcur)
	{
		QN* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->head = pq->tail = NULL;
}
//队列判空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}
//出队---队头
void QueuePop(Queue* pq)
{
	assert(!QueueEmpty(pq));
	//队头和队尾相等,即只有一个节点
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QN* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}
//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->head->data;
}
//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}
//队列中有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);
	//方法一:遍历队列(适用于不频繁使用数据个数的情况)
	QN* pcur = pq->head;
	int size = 0;
	while (pcur)
	{
		size++;
		pcur = pcur->next;
	}
	return size;
	//方法二:在队列的结构体中添加变量size
	// 并在初始化时初始化为0,每当添加节点时就让它计数
	//(适用于频繁使用数据个数的情况)
	//return pq -> size;
}

test.c

#include "Queue.h"

void test()
{
	Queue plist;
	QueueInit(&plist);
	QueuePush(&plist, 1);
	QueuePush(&plist, 2);
	QueuePush(&plist, 3);
	QueuePrint(&plist);
	printf("%d",QueueSize(&plist));
	QueueDestroy(&plist);
}

int main()
{
	test();

	return 0;
}

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~