大家好,上期介绍了数据结构-栈的内容,这期咱们接着来看看数据结构中-队列的内容,一起来看看吧!!!
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定义节点
- 我们用链表实现队列,链表是由一个个节点组成的,因此我们需要定义节点的结构,这里用结构体指针。
- 一个节点是由数据和指向下一个节点的指针组成。
清楚这两点,我们就很快写出来了。
#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;
}