【数据结构】队列的实现

本篇总结队列是如何实现的,以及各方面的细节问题,比较链表与队列之间的不同,注意到不同的类型,要按照不同需求来定义结构,不能一成不变,印象刻板。

【队列】

⏰.队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,
队列具有先进先出FIFO(First In First Out)的特点。
入队列:进行插入操作的一端叫做队尾

出队列:进行删除操作的一端叫队头
在这里插入图片描述

⏰.队列的实现

队列可以用数组和链表来实现。但使用链表的结构更优一些,因为如果使用数组结构,删除数据需要挪动大量数据,效率比较低。

在这里插入图片描述

队列我们使用链表来实现
首先使用链表定义一个结点
我们都知道结点是由一个结构体定义的,这个给结构体里有个指向下一个结点地址的next和数据data。
但想一下,队列需要对链表进行尾删,也就是每次都要找到尾结点,将链表的头结点和尾结点传给出队列函数,然后进行出队列操作,这样传参数就传好几个,不如让我们将头结点的地址和尾结点的地址分装成一个新的结构体

想一想为什么这里要给个尾指针过去呢?
如果在实现单链表操作中也给尾指针呢?
单链表中给尾指针能完成操作吗?尾插操作需要尾指针,这个操作可以完成,但尾删呢?尾删操作你给个尾指针就能完成吗?当然不能,还需要尾指针前面的结点地址。传个尾指针过去只能完成部分操作,不能完成全部操作还不如不传,代码挫的一批。

但这里不一样,这里队列有它自己的规则:那就是只在一端进行入队,那就是队尾,也就是说在队尾只进行尾插,不进行尾删,所以这里传尾指针过去是可以完成队列所需要的操作的,

要区别单链表中只传一个头指针,而队列这里要传头指针和尾指针的不同,我们要按照需要来定义不同结构。

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDatatype;
typedef struct QNode//定义结点结构
{
	struct QNode* next;
	QDatatype data;
}QNode;

typedef struct Queue//将头指针和尾指针分装起来,传给队列操作函数
{
	QNode* head;//链表头指针
	QNode* tail;//链表尾指针
	int size;//大小想一想传过去有什么好处?
}Queue;

//初始化队列
void  QueueInit(Queue* pq);
//销毁队列
void  QueueDestroy(Queue* pq);
//队尾入队列
void  QueuePush(Queue* pq,QDatatype x);
//队头出队列
void  QueuePop(Queue* pq);
//获取队列的有效数据的大小
void  QueueSize(Queue* pq);
//判断队列是否为空,如果为空返回非0,如果为非空返回0
bool  QueueEmpty(Queue* pq);
//获取队列头部元素
QDatatype  QueueFront(Queue* pq);
//获取队尾元素
QDatatype  QueueBack(Queue* pq);

🕐1.初始化队列

首先对传入的数据进行初始化,我们知道传过来的是个分装好的结构体,里面有链表头指针,尾指针,大小。
所以一开始我们需要将头指针尾指针和大小都置0;

//初始化队列
void  QueueInit(Queue* pq)
{
	assert(pq);//首先断言判断是否传错
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

🕑2.队尾入队列

队尾入队列,实际上就是链表的尾插。
而这正是这操作我们将链表的尾指针也传过去,就是为了每次尾插不需要找尾,每次插入时更新下尾指针位置即可。

//入队列
void  QueuePush(Queue* pq, QDatatype x)
{
	assert(pq);//断言是否传错
	//入队,就是尾插一个结点,首先生成一个新结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	newnode->next = NULL;//需要置空,不然会出现野指针
	newnode->data = x;//将数据赋值
	if (newnode == NULL)//判断是否开辟成功
	{
		perror("malloc");
	}
	//因为为单链表,不带哨兵头,所以一开始head和tail都为空,第一步直接将结点赋值过去,然后后面才是真正的尾插
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;//将结点赋值过去
	}
	else
	{
		//尾插
		pq->tail->next = newnode;
		pq->tail = newnode;//找尾--更新
	}
	pq->size++;//插入一个数据size就要++
}

🕒3.队头出队列

对头出队列,本质上就是链表的头删,头删就是让头指针不断改变,然后让头指针前面的结点释放。

注意点1:
在删除数据之前我们都要对队列进行检查,看是否队列为空。
注意点2:要注意tail是指向最后一个结点的指针。
当head指向最后一个结点后释放,那么tail指向的空间被释放,那tail就变成野指针了,所以最后需要将tail置NULL。

有两中方式:
第一种:只管头删,最后再将tail置NULL
第二种:一开始就考虑二种情况,最后一个结点之前,和最后一个结点,即分为一个结点和多个结点。

//出队列
void  QueuePop(Queue* pq)
{
	assert(pq);
	//在删除数据之前需要考虑队列是否为空;
	assert(!pq->head==NULL);
		QNode* next = pq->head->next;//记录头结点后面的结点
		free(pq->head);//是否头节点
		pq->head = next;//将头指针重新指向后面的结点
		//注意如果最后head和tail指向同一个结点,也就是最后一个结点时,tail会变成野指针。
	//方法1:
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
	pq->size--;
}

方法2:

void  QueuePop(Queue* pq)
{
	assert(pq);
	//在删除数据之前需要考虑队列是否为空;
	assert(!pq->head==NULL);
		//注意如果最后head和tail指向同一个结点,也就是最后一个结点时,tail会变成野指针。
	if (pq->head->next == NULL)
	{
		//直接释放最后一个结点
		free(pq->head);
	   pq->head = pq->tail = NULL;
	 }
	else
	{
		QNode* next = pq->head->next;//记录头结点后面的结点
		free(pq->head);//是否头节点
		pq->head = next;//将头指针重新指向后面的结点
	}
		pq->size--;
}

🕓4.获取队列头部元素

获取队头数据,就直接将头结点的数据拿走即可,不过在拿之前需要进行判断队列是否为空,如果为空,那还拿什么

//获取队头数据
QDatatype  QueueFront(Queue* pq)
{
	assert(pq);//断言判断是否传错
	assert(!QueueEmpty(pq));//判断是否为空
	return pq->head->data;//队头数据
}

🕔5.获取队列尾元素

和获取队头数据一样需要判断队列是否为空

//获取队尾数据
QDatatype  QueueBack(Queue* pq)
{
	assert(pq);//断言判断是否传错
	assert(!QueueEmpty(pq));//判断是否为空
	return pq->tail->data;//队尾数据

}

🕕6.获取队列中有效元素的个数

看,最开始的问题即将揭晓,在分装的结构体中加上size的作用是什么?
加上size后我们就可以快速获取队列中有效元素的个数了,
如果没有加上,要获取个数,那必须对链表进行遍历,时间复杂度就是O(N),而现在就是O(1)。同样在单链表中我们如果想快速获得链表中有效数据的个数,也可以将节点与size分装成一新的结构体。那样就可以啦。

有的人想在带有哨兵头的链表中快速获取个数的办法是将size放入哨兵头里,想着反正哨兵头也不存储数据,就想着将size放进去,但是可以吗?

当然不可以,我们定义的链表中数据的类型有时是知道的,有时是不知道的,当数据类型是int类型是哨兵头里面可以放size,但当链表使用的数据类型是char? double 类型时,你放进去一个int类型的size进去?可以吗?

void  QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

🕖7.检查队列是否为空

bool  QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

🕗8.销毁队列

与头删类似,就是循环删除。当删除为空时,停止
然后将数据都置0

//销毁队列
void  QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//保存下一个释放,保存下一个释放
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陶来咯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值