【数据结构】队列

队列是什么

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

队列的数据结构可以想象成我们的食堂吃饭排队,先排的先走

与栈一样队列的结构是规定死的,即插入必须在队尾插入,删除只能在队头,必须满足先进先出原则

队列的实现

  • 数组队列
  • 链表队列

在这里插入图片描述

我们用数组实现的时候,尾插和头删都会遇到一个难点,即需要挪动数据,时间复杂度O(N)

而我们的链表实现不需要,在已知尾结点地址情况下,时间复杂度是O(1)

定义结构体

我们定义结构体,一想就是

typedef int datatype;

typedef struct Qnode
{
	struct Qnode* next;
	datatype data;
}Qnode;

但为了方便我们的尾插和头删,我们要定义2个结构体指针指向尾和头

也就是说,在初始化的时候,我们要定义这2个指针,并且每一次插入删除,都要手动使头尾指针改变

尤其这样,我们不如再定义一个结构体

typedef int datatype;

typedef struct Qnode
{
	struct Qnode* next;
	datatype data;
}Qnode;

typedef struct Queue
{
	Qnode* head;
	Qnode* tail;
	int size;
}Queue;

我们要不要将2个结构体合二为一?

不需要。

  • 我们的QNode是定义的结点的结构,即我们的整个链表
  • Queue是定义的指向链表中头尾指针的结构

在这里插入图片描述

我们的每一个结点需要有头尾指针和size吗?不需要!

都是各司其职的,合二为一反而加大维护成本

void test1()
{
    Queue q;
	queueinit(&q);
    
	queuedestroy(&q);
}

我们这样写就不需要用二级指针了,因为我们是要改变结构体,只需要传结构体指针即可,即一级指针

初始化函数

void queueinit(Queue* pst)
{
	assert(pst);
    
	pst->head = NULL;
	pst->tail = NULL;
	pst->size = 0;
}

这里,我们清楚我们定义的结构体Qnode是帮助我们创建链表结点的,我们的结构体Queue包含整个链表的头尾指针,所以我们将定义链表的头指针返回给Queue来保存,也就是说我们是操作多半是针对Queue的

free函数

void queuedestroy(Queue* pst)
{
	assert(pst);
	Qnode* cur = pst->head;
	while (cur)
	{
		Qnode* next = cur->next;
		free(cur);
		cur = next;
	}
	pst->head = pst->tail = NULL;
	pst->size = 0;
}

free你malloc出来的东西

值得注意的是,free完后,要给pst->head pst->tail置空,并且让size=0,不让就是野指针

判空函数

bool queueempty(Queue* pst)
{
	assert(pst);
    
    // return pst->size == 0;
	return pst->head == NULL && pst->tail == NULL;
}

我们可以用2个比较操作符来用于返回值,在栈的章节有讲

入队列

void queuepush(Queue* pst, datatype x)
{
	assert(pst);
	Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));
	if (!newnode)
	{
		perror("malloc");
		return;
	}
	newnode->next = NULL;
	newnode->data = x;

	if (!queueempty(pst))
	{
		pst->tail->next = newnode;
		pst->tail = newnode;
	}
	else
	{
		pst->head = pst->tail = newnode;
	}
	pst->size++;
}
Qnode* newnode = (Qnode*)malloc(sizeof(Qnode));

注意这里的malloc的类型是Qnode不是其他的类型,要知道自己想要malloc什么结点出来

尾插后,要改变头尾指针和size

出队列

void queuepop(Queue* pst)
{
	assert(pst);
	assert(!queueempty(pst));
	
	if (!pst->head->next)
	{
		free(pst->head);
		pst->head = pst->tail = NULL;
	}
	else
	{
		Qnode* next = pst->head->next;
		free(pst->head);
		pst->head = next;
	}
	pst->size--;
}

我们要考虑只有一个结点和多个结点的情况

很多人认为单纯这样即可

Qnode* next = pst->head->next;
free(pst->head);
pst->head = next;

那请问,

  • 队列为空的情况下呢,
  • 只有一个结点的情况下呢,多个呢?
  • size需不需要–?
  • 一个结点时,我们想一想,头尾指针是不是都是指向同一个结点的,如果我们单纯将头指针free掉,那尾指针是不是就是野指针了
  • 多结点时,上面代码可行
  • 没有结点时,需要我们assert判空
  • 最后别忘了将size–。

为了避免想当然,我们在写代码的时候应该多画图多思考

取队头元素

datatype queuefront(Queue* pst)
{
	assert(pst);
	assert(!queueempty(pst));

	return pst->head->data;
}

唯一值得注意的是,要判空,不然没有队列为空还在访问

取队尾元素

datatype queueback(Queue* pst)
{
	assert(pst);
	assert(!queueempty(pst));

	return pst->tail->data;
}

在我们官方的文档下面,是有取队尾元素的操作的

在这里插入图片描述

获取元素个数

与我们栈一样,我们也需要用一个函数来获取

datatype queuesize(Queue* pst)
{
	assert(pst);
	return pst->size;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值