数据结构-队列

大家好,上期介绍了数据结构-栈的内容,这期咱们接着来看看数据结构中-队列的内容,一起来看看吧!!!

1.队列的概念

1.1队列的含义

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

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

类比成一个隧道:先进去的车先出来,隧道入口叫做队尾,隧道出口叫做队头。车从队尾进入隧道就是入队列,车从队头出隧道就是出队列。
在这里插入图片描述

1.2队列与栈的区别

入栈顺序:1 出栈顺序:N 1对多
入队列:1 出队列:1 1对1

应用:
1.买奶茶
买奶茶排队问题:就相当于一个队列,先预定的人先排在队列中,一定先得到奶茶,保证公平性;
如何知道前面还有多少号呢?取队头数据,把你两的号一减就知道了还有几个人轮到自己。

2.广度优先遍历
好友推荐问题:qq系统会根据共同好友给用户推荐可能认识的人,这里就涉及到先推荐谁的问题,当然是与用户联系程度高的好友。
在这里插入图片描述
比如,我们要找小徐的好友的好友,该怎么办呢?
先把小徐入队列,再出队列,小徐的好友:小明和小王入队列,此时,队列里是小徐的好友;我们再分别把小明和小王出队列,他们两的好友入队列,那么此时,队列里的就是小徐的好友的好友啦!先进先出

2.队列的实现

2.1实现思路

队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
需要在一端入数据,另一端出数据,因此首先排除数组。使用单链表即可。

在这里插入图片描述
在这里插入图片描述

//初始化
void QueueInit(Queue* pq);
//队尾插入
//void QueuePush(QNode**phead,QNode**ptail,int x);
void QueuePush(Queue* pq, QDataType x);
//队头删除
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);

2.2定义节点

  1. 我们用链表实现队列,链表是由一个个节点组成的,因此我们需要定义节点的结构,这里用结构体指针。
  2. 一个节点是由数据和指向下一个节点的指针组成。
    清楚这两点,我们就很快写出来了。
    在这里插入图片描述
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
//用单链表实现队列
typedef int QDataType;
//定义节点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType val;
}QNode;

2.3初始化

队列:先进先出,也叫尾插头出,进出顺序是唯一的。
为了方便后续操作,我们需要使用两个指针,头指针和尾指针。

参数传递原则:在C++中,参数传递是值传递的,直接修改形参不会改变实参。但是,可以通过二级指针修改形参指针指向的内容,因为二级指针指向的内容就是一个一级指针。

因此,我们若想要通过修改形参来改变实参,值传递是不行的,得用二级指针修改形参指针指向的内容。即:

//队尾插入
//void QueuePush(QNode**phead,QNode**ptail,int x);

这里我们可以在定义一个节点的结构体指针,存放头尾两个节点指针,既方便书写,也避免使用二级指针,简化了代码。即:

//再定义一个结构体指针,放头尾节点指针
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

//队尾插入
//void QueuePush(QNode**phead,QNode**ptail,int x);
void QueuePush(Queue* pq, QDataType x);
//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

2.4队尾插入

队列是用链表实现的,而链表是由节点组成的,因此在插入之前得先为节点开辟内存malloc。并检查内存是否开辟成功,成功后,则开始插入。

  • 动态申请新节点内存: malloc,申请完检查是否成功
  • 初始化新节点:新节点是队尾,所以next指向NULL;数据x
  • 将新节点插入队尾:考虑队列是否为空
  • 更新队列大小:pq->size++
//队尾插入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	//动态申请新节点内存
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)//检查内存分配是否成功
	{
		perror("malloc fail!");
		return;
	}
	//初始化新节点
	newnode->next = NULL;//新节点是队尾,所以next是NULL
	newnode->val = x;

	//将新节点插入队尾
	if (pq->ptail == NULL)//情况①:队列为空(phead 和 ptail 都为 NULL)
	{
		//队列为空
		pq->phead = pq->ptail = newnode; 新节点既是头也是尾
	}
	else
	{
		//队列不为空
		pq->ptail->next = newnode;//原队尾的next指向新节点
		pq->ptail = newnode;//更新ptail指向新队尾
	}
	pq->size++;//更新队列大小
}

2.5队头删除

队头删除,不单单是简单的free,将指针置空,还需要考虑只有一个节点的情况,这也往往是容易出错忘记的地方。
在这里插入图片描述

//队头删除
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size!=0);

	//只有一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else//多个节点
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;
}

2.6获取队头元素

// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	return pq->phead->val;
}

2.7获取队尾元素

// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);

	return pq->ptail->val;
}

2.8获取有效元素个数

// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

2.9判空

// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

2.10销毁队列

在这里插入图片描述

  • 队列是由链表实现的,链表是由节点组成的,故销毁队列就是释放所有节点。
  • 遍历队列,逐个释放节点。
  • 从头节点开始,为防止改变头节点,我们新定义一个指针cur
  • 循环遍历所有节点,直到cur为NULL,即遍历完整个队列。
  • 必须先保存下一个节点,才能释放当前节点,否则后续无法访问。
  • 释放完一个节点,移动cur到下一个节点next,继续上述操作。
  • 释放完要重置队列状态
//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);  // 确保队列指针有效

	// 1. 遍历队列,逐个释放节点
	QNode* cur = pq->phead;  // 从头节点开始
	while (cur != NULL)      // 遍历直到队尾
	{
		QNode* next = cur->next;  // 先保存下一个节点
		free(cur);                // 释放当前节点
		cur = next;               // 移动到下一个节点
	}

	// 2. 重置队列状态
	pq->phead = NULL;  // 头指针置空
	pq->ptail = NULL;  // 尾指针置空
	pq->size = 0;      // 队列大小归零
}

2.11测试

在这里插入图片描述
在这里插入图片描述

3.完整代码

3.1 Queue.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>


//用单链表实现队列

typedef int QDataType;
//定义节点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType val;
}QNode;

//再定义一个结构体指针,放头尾节点指针
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

//初始化
void QueueInit(Queue* pq);
//队尾插入
//void QueuePush(QNode**phead,QNode**ptail,int x);
void QueuePush(Queue* pq, QDataType x);
//队头删除
void QueuePop(Queue* pq);
// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);
// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);

3.2 Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
//队尾插入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);

	//动态申请新节点内存
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)//检查内存分配是否成功
	{
		perror("malloc fail!");
		return;
	}
	//初始化新节点
	newnode->next = NULL;//新节点是队尾,所以next是NULL
	newnode->val = x;

	//将新节点插入队尾
	if (pq->ptail == NULL)//情况①:队列为空(phead 和 ptail 都为 NULL)
	{
		//队列为空
		pq->phead = pq->ptail = newnode; 新节点既是头也是尾
	}
	else
	{
		//队列不为空
		pq->ptail->next = newnode;//原队尾的next指向新节点
		pq->ptail = newnode;//更新ptail指向新队尾
	}
	pq->size++;//更新队列大小
}

//队头删除
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size!=0);

	//只有一个节点
	if (pq->phead->next == NULL)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else//多个节点
	{
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	pq->size--;

}

// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->phead);

	return pq->phead->val;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->ptail);

	return pq->ptail->val;
}
// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* pq)
{
	assert(pq);

	return pq->size == 0;
}

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);  // 确保队列指针有效

	// 1. 遍历队列,逐个释放节点
	QNode* cur = pq->phead;  // 从头节点开始
	while (cur != NULL)      // 遍历直到队尾
	{
		QNode* next = cur->next;  // 先保存下一个节点
		free(cur);                // 释放当前节点
		cur = next;               // 移动到下一个节点
	}

	// 2. 重置队列状态
	pq->phead = NULL;  // 头指针置空
	pq->ptail = NULL;  // 尾指针置空
	pq->size = 0;      // 队列大小归零
}

3.3 test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

#include"Queue.h"

int main()
{
	Queue q;

	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);

	//访问队列所有元素
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值