【栈和队列】链式队列详解

引言

一张图先来看看什么是队列:

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

入队列:进行插入操作,即尾插。

出队列:进行删除操作,即头删。

因为要进行头头插,所以本篇选用无头单向非循环链表(以下简称单链表)作为队列。其中用到的单链表相关知识可以参考我之前的博客:【线性表】单链表详解(C语言版)_是兰兰呀~的博客-CSDN博客


一、定义

既然要用单链表作为队列,那么首先要像单链表那样定义节点,具体操作参考前面链接,在此不再赘述。

有了节点其实还不够,对于队列,我们经常会对其进行插入,即链表的尾插,很显然,如果每次尾插都遍历一下链表,那将会特别麻烦,而且浪费时间,所以咱不妨再定义一个尾指针tail。为了避免每次调用函数不仅要传头指针head还要传尾指针tail,并且插入还得传head的二级指针,我们不妨再将链表的头指针head和链表的尾指针tail封装到一个结构体中,当然这个结构体里面还可以再加上一个size表示该队列长度,每次插入size++,每次删除size--,也免得计算队列长度还需要遍历链表。

由此,我们就有了如下图所示的结构体:

每个QNode节点都是一个结构体,该结构体包含数据data和指向下一结构体的指针next。head和tail是节点结构体类型的指针,分别指向链表的头节点和尾节点。size表示链表,即队列长度。

OK,看代码:

//节点定义
typedef int QDataType;//定义QDataType为int,方便以后修改存储的数据类型

typedef struct QueueNode
{
	QDataType data;//节点数据
	struct QueueNode* next;//指向下一个节点的指针
}QNode;

//队列定义
typedef struct Queue
{
	QNode* head;//头指针
	QNode* tail;//尾指针
	int size;//队列长度
}Queue;

二、初始化

在初始化中,我们要处理的只有Queue,因为在主函数中,我们只是用“Queue qu;”这条语句创建了一个Queue的结构体qu,此时qu->head、qu->tail、qu->size均为随机值,对于QNode,并没有开辟任何空间,所以也不需要处理。

这样一来,很显然了,我们只需对qu初始化就好,将这个结构体的地址传过去(因为我们要改变qu内的数据),指针置空,其余置0,嗯,完美。

void QInit(Queue* qu)
{
	assert(qu);//qu不为空

	qu->head = NULL;//置空头指针
	qu->tail = NULL;//置空尾指针

	qu->size = 0;//队列长度为0
}

三、销毁

销毁最主要一点就是得考到队列里有数据的情况,我们得挨个释放所有节点。

 定义两个指针,cur用来释放当前节点,next用来记录释放节点的下一个节点,防止释放后链表的丢失。

 

释放cur指向节点,注意,释放之后,cur和head均为野指针。

令cur等于next,开始新的循环。 

直到cur == NULL,循环终止。 注意,此时tail指针也为野指针。

 

修改head和tail为空指针,size为0。 

看看代码:

void QDestroy(Queue* qu)
{
	assert(qu);//qu不为空

	QNode* cur = qu->head;//cur遍历队列释放每个节点空间
	while (cur)
	{
		QNode* next = cur->next;//next记录要删除节点的下一个

		free(cur);//释放当前空间
		
		cur = next;//指针后移,方便删除下一个
	}

	qu->head = NULL;//置空头指针
	qu->tail = NULL;//置空尾指针
	qu->size = 0;//队列长度为0
}

四、插入

因为没有头插、尾插、中间插入之类的乱七八糟的插入,我们省去了链表中开辟节点的函数,将其直接并到了插入函数中。

前面讲过吧,队列的插入就是链表的尾插,我们直接回忆一下单链表是怎么开辟节点和尾插的:

1.开辟节点;2.修改新节点的数据和指针;3.讨论链表是否为空;4.为空令头指针指向新结点;5.不为空尾插入链表。

队列无非是在此基础上增加一个修改尾指针就可以了。

开辟新节点。 

 修改新节点的数据和指针。

若链表为空,修改头指针和尾指针。

若链表不为空,尾插入链表并修改尾指针。 

修改size。 

void QPush(Queue* qu, QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));//为新节点开辟空间

	if (newnode == NULL)//开辟失败
	{
		perror("QPush");//打印开辟失败
		exit(-1);//程序以非正常形式结束
	}

	//开辟成功
	newnode->data = x;//新节点数据为x
	newnode->next = NULL;//新节点的next为空

	if (qu->tail == NULL)//队列中无元素
	{
		qu->head = newnode;//修改头指针,指向新节点
		qu->tail = newnode;//修改尾指针,指向新节点
	}

	else//队列有元素
	{
		qu->tail->next = newnode;//将新节点尾插入链表
		qu->tail = newnode;//修改尾指针指向
	}

	qu->size++;//队列长度加1
}

五、删除

队列的删除其实就是链表的头删。

headnext记录头节点的后一个节点。 

释放头指针,将head指向headnext。 

修改size。 

若队列只有一个节点。 

和前面一样,head指向headnext,并修改size。 

与前面不同的是,若只有一个节点,需修改tail指针为空。 

 

void QPop(Queue* qu)
{
	assert(qu);//qu不为空
	assert(!QEmpty(qu));//队列不为空

	QNode* headnext = qu->head->next;//next记录要删除节点的下一个

	free(qu->head);//释放头节点
	qu->head = headnext;//原先的第二个节点变为新的头节点

	qu->size--;//队列长度减1

	if (qu->head == NULL)//队列删完了
	{
		qu->tail = NULL;//将尾指针置空,避免变成野指针
	}
}

六、取队头数据

取对头元素只需要返回队列第一个节点的数据就可以了。

QDataType QFront(Queue* qu)
{
	assert(qu);//qu不为空
	assert(!QEmpty(qu));//队列不为空

	return qu->head->data;//返回头节点数据
}

七、取队尾数据

这个返回最后一个节点的数据就好。

QDataType QBack(Queue* qu)
{
	assert(qu);//qu不为空
	assert(!QEmpty(qu));//队列不为空

	return qu->tail->data;//返回尾节点数据
}

八、判空

这个函数的返回值我们设为bool类型,空返回真,非空返回假。

bool QEmpty(Queue* qu)
{
	assert(qu);//qu不为空

	return qu->head == NULL;//返回头指针为空指针的真值
}

九、队列长度

因为前面封装结构体时咱定了一个size,所以这一步就简单了,没必要遍历队列,直接返回size就OK。

int QSize(Queue* qu)
{
	assert(qu);//qu不为空

	return qu->size;
}

结尾

本篇代码码云指路:class_c: 课上要认真鸭~ - Gitee.com

若有错误,欢迎大家批评斧正! 

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

是兰兰呀~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值