数据结构——队列

队列的概念

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

在这里插入图片描述

选择用链表实现队列

我们这里选择使用链表去实现队列,原因如下:
因为队列在尾进在头出,如果用顺序表实现,那么出队(头删)就需要挪动顺序表的数据,效率低。

所以这里选择链表去实现

在选择链表时,可选使用头指针的链表也可以使用带头哨兵位头节点的链表,但是这里哨兵位节点并不会发挥很大的作用,所以这里选择用带头指针的链表实现

队列的逻辑图和物理实现图对比如下:
在这里插入图片描述
headtail指针指向头和尾,在头部出队,只需将head指向下一个节点;在尾部入队,只需尾插一个节点即可,所以这么实现会比较简单

这里要注意的一点,在图中,逻辑图中箭头顺序为从右到左,物理图中箭头由左到右。它们表示的意思不一样,不要被相反的顺序影响自己的逻辑


队列的实现

我们先写一个链表的结构体:

typedef int QueueDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QueueDataType data;
}QNode;

在主函数中,为了方便,我们定义一个头指针head指向队头和尾指针tail指向队尾

QNode* head = NULL;
QNode* tail = NULL;

如果这么写的话,在设计函数时,就要至少传2个指针,并且为二级指针,这是否的麻烦

所以我们可以再定义一个结构体,将headtail都放到这个结构体中,那么设计函数时只需要传一个指针就可以了

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

这样设计的话,函数中传这个结构体的参数,通过结构体指针就可以改变结构体了,所以也不用传二级指针了

这里在结构体内还定义了一个int size,这是表示队列的长度,这个设计并非多余,在后面的实现中就会发现这个size能提高效率

这里的队列实现需要定义2个结构体,这是在之前的数据结构中没有出现的


初始化

void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = pq->tail = NULL;
	pq->size = 0;
}

headtail都初始化为空,size为0


销毁

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* nex = cur->next;
		free(cur);
		cur = nex;
	}
	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

入队

入队,首先就是开辟一个新节点

QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

然后就是入队了,在队尾入队
这里如果head==NULL,就说明当前队列还没有元素headtail都为空
所以让head指向这个第一个节点,同时这个节点也是自己的尾,tail也应指向这个节点

pq->head = pq->tail = newnode;

如果head不为空,就说明当前队列中有元素,直接在tail后面插入新节点即可

void QueuePush(Queue* pq, QueueDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = pq->tail->next;
	}
	pq->size++;

}

出队

出队,首先应用断言判断一下队列是否为空

assert(!QueueEmpty(pq));

在队头出队

首先用next指针保存head->next,然后就可以freehead了,然后再将head指向新的头next

QNode* next = pq->head->next;
free(pq->head);
pq->head = next;

在这里会发现,如果队列只要一个节点,next为空,然后虽然能够让head为空,但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;
	}

所以最终代码:

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	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--;
}

判空

bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

队列长度

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

这里就最能看出在结构体中定义size的好处了
如果不去定义,那么在这里求长度就会遍历一遍队列,导致效率低

而之前定义了size,这里直接返回size即可,效率高


返回队头元素

QueueDataType QueueFront(Queue* pq)
{
	assert(pq);
	return pq->head->data;
}

一般通过QueueFrontQueuePop2个函数来得到队列中的数据

打印队列中数据:

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}

返回队尾元素

QueueDataType QueueBack(Queue* pq)
{
	assert(pq);
	return pq->tail->data;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疯癫了的狗

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

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

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

打赏作者

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

抵扣说明:

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

余额充值