【数据结构】队列的实现

本文详细介绍了队列的概念、结构以及在C语言中使用链表实现的具体步骤,包括队列的特性、队头出队尾入操作、初始化、判断队列状态、数据操作等,强调了队列在数据结构中的重要性和实现技巧。
摘要由CSDN通过智能技术生成


前言

数据结构的常见线性表,分别是顺序表,链表,栈,队列

本篇给大家带来链表结构实现队列和讲解,这也是线性表结构的最后一篇,大家一起共勉


1. 队列的概念及结构

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

2. 队列的实现设计

队列的特性是队头出数据,队尾入数据,这里同样顺序表链表都可以实现

  • 链表:头插效率高,可是尾插需要找尾啊,同样效率不高
  • 顺序表:涉及到头部操作,顺序表就显得无能为力

顺序表结构是固定死了的,链表可以变形,我们只需要在多定义一个尾结点即可,说道这里,肯定会有疑问,为什么之前单链表的时候不这样做,因为单链表尾删需要找尾的前一个,可队列只需要尾插,不需要找前一个。
综上所诉,我推荐用链表实现,不过需要额外定义了一个尾结点
在这里插入图片描述

3. 栈的功能实现

首先我们看下结构类型的定义和实现功能

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

// 定义队列
// 队列里面有头和尾结点,头和尾结点的类型是单个节点的结构
typedef struct Queue
{
	QueueNode* head;
	QueueNode* tail;
}Queue;

// 初始化
void QueueInit(Queue* pq);

// 判断队列是否为空
bool QueueEmpty(Queue* pq);

// 入队
void QueuePush(Queue* pq, QDataType x);

// 出队
void QueuePop(Queue* pq);

// 获取队头数据
QDataType QueueFront(Queue* pq);

// 获取队尾数据
QDataType QueueBack(Queue* pq);

// 队列的大小
int QueueSize(Queue* pq);

// 销毁
void QueueDestroy(Queue* pq);

这里说明下这定义两个结构的作用和区别
首先我们用链表的话,需要定义单个节点(data和next),目的是存数据和通过指针来链接下一个节点,这就是结构体QueueNode
这里又定义了一个队列结构体(Queue),目的是放下头和尾两个结点,因为这两个节点实现了队列和方便访问头和尾,这两个结点类型都是单个节点结构(QueueNode),这就是结构体Queue
简单来说就是嵌套结构,队列里两层结构

  • 第一层:方便访问头和尾。
  • 第二层:通过头和尾访问data和next

如访问队列头节点的数据:队列的头的data,q->head->data

3.1. 初始化

// 初始化
void QueueInit(Queue* pq)
{
	assert(pq);

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

3.2. 判断队列是否为空

如果队列的头节点为NULL返回true,不为空返回false

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

	return pq->head == NULL;
}

3.3. 入队(队尾插入数据)

  1. 创建新节点
  2. 插入新节点,分两种情况
  • 第一种:队列为空,头和尾就需要赋值为新节点,头赋值是了标志第一个结点,尾赋值是方便下一次插入数据
  • 第二种:队列不为空,尾指针的next链接新节点,在更新尾结点
// 入队
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	// 创建新节点
	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
	assert(newNode);
	newNode->data = x;
	newNode->next = NULL;

	// 插入数据分两种情况
	// 1. 队列为空:头和尾赋值为新节点
	// 2. 队列不为空:直接尾插,在更新尾结点
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newNode;
	}
	else
	{	
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}

3.4. 出队(队头删除数据)

  1. 队列为空不能删除
  2. 保存头节点下一个节点,防止free后,就找不到的情况
  3. 释放头节点
  4. 刚才保存的结点设新的头
// 出队
void QueuePop(Queue* pq)
{
	assert(pq);
	// 空队列不能删除
	assert(!QueueEmpty(pq));
	
	// 头删
	QueueNode* headNext = pq->head->next;
	free(pq->head);
	pq->head = headNext;
	
	// 头为空表示队列空了,因为上面的步骤只把头给置空,尾没有,tail就是野指针了
	if (pq->head == NULL)
		pq->tail = NULL;
	
}

3.5. 获取队头数据

// 获取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	// 空队列不能获取数据
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

3.6. 获取队尾数据

注意:空队列不能获取数据

// 获取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	// 空队列不能获取数据
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

3.7. 队列的大小

链表只能通过遍历来获取数据个数,条件是为cur==NULL结束

// 队列的大小
int QueueSize(Queue* pq)
{
	assert(pq);
	
	int size = 0;
	QueueNode* cur = pq->head;
	while (cur)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

3.8. 销毁

类似于单链表的销毁,只不过最后需要把头和尾都设为NULL

// 销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	
	QueueNode* cur = pq->head;
	while (cur)
	{
		QueueNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

4. 功能测试

队列和栈一样是不允许遍历,由于只有队头和队尾两端的数据可以被使用,但是只有队头能取数据,所以要把队头的数据取了,才能取下一个,要实现打印数据,需要一个一个取数据,在出队,队列为空结束
在这里插入图片描述


5. 总结

链表学习完,实习队列并不难,只不过在单链表和基础上多添加了尾结点,因此造成嵌套结构体,一共两层,主要理解和熟悉队列的特性(先进先出)和链表结构的多样性

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值