一、队列
1、队列的概念及结构
队列:是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(First In First Out)的特点。
入队列:进行插入操作的一端称为队尾。
出队列:进行删除操作的一端称为队头。
如图所示:
1.2队列的应用:
1.任务调度:多个任务按照顺序排队等待执行。
2.广度优先搜索(BFS):在图或树的遍历中,按层次遍历节点。
3.缓存管理:缓存中的数据按照访问顺序排列,最先进入的数据最先被替换。
4.算法设计:某些算法的设计与队列密切相关,如哈夫曼编码、循环队列等。
1.3队列存在的优缺点:
优点:
1、先进先出(FIFO):队列保持元素的插入顺序,对线插入的元素最先被处理。
2、简单高效:队列的基本操作包括入队和出队,它们的时间复杂度都是O(1),无论队列的大小如何,操作的时间复杂度都是固定的。
3、应用广泛:队列在很多算法和实际应用场景中都有着广泛的应用,如任务调度、缓存管理、广度优先搜索。
缺点:
1、随机访问困难:队列只允许在队头删除元素,在队尾插入元素,不支持随机访问。如果需要在队列的其他位置插入或删除数据,运行效率较低。
2、队列大小的限制:使用数组实现的队列在创建时需要指定最大容量,因此队列中的元素有限。如果队列已满,则无法插入新的数据。
3、存储空间浪费:如果队列中实际的元素个数远小于最大容量,那么可能造成存储空间的浪费,因为队列的空间是固定的。
1.4队列的基本结构
本文队列采用链式存储进行讲解,而非数组存储。
1.4.1链式存储的原因
基于队列先进先出的特点,队列先进的数据从队尾进来,接着从队头出去,链式存储利用指针将一个一个的元素连接起来。插入数据,则用指针在队尾链接数据;删除数据,则释放掉队头元素。
相较于链式存储,数组存储在队尾插入数据简单(时间复杂度为O(1)),但由于删除数据需要在队头进行,还需把后面的数据通通往前移一位,由此以来,时间复杂度就将变为O(N),效率变低,所以不推荐使用。
1.4.2存储结构
typedef int QDatatype; //暂定为整数类型
typedef struct QueueNode
{
QDatatype val; //存放数据
struct QueueNode* next; //存放链
}QNode;
但仅仅这些还不够,我们还要标记队列的队头和队尾,以方便插入和删除。这样一来,我们队列插入和删除操作的时间复杂度就变为了O(1)。
typedef struct Queue
{
QNode* head; //标记队头指针,用于删除数据
QNode* tail; //标记队尾指针,用于插入数据
int size; //标记队列中有效元素个数
}Queue;
ps:这是一种重要的算法思维(以空间换时间),提高算法效率。
1.5队列的基本操作
- QueInit(&pq); 初始化一个空队列
- QuePush(&pq, x); 往队列新插入一个数据,并将其放在队尾
- QuePop(&pq); 删除队列中队头的元素,并释放
- QueFront(&pq); 获取处在队列队头的元素数据
- QueBack(&pq); 获取处在队列队尾的元素数据
- QueEmpty(&pq); 判断队列是否为空
- QueSize(&pq); 返回获取队列中元素的个数
- QueDestroy(&pq); 销毁队列,并释放pq所占用的存储空间
2、队列的实现算法
1.初始化
void QueInit(Queue* pq)
{
pq->head = NULL;
pq->tail = NULL; //将队列的头尾指针置为空
pq->size = 0; //队列长度初始化为零
}
2.入队列
void QuePush(Queue* pq, QDatatype x)
{
//判空
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
//检验是否创建成功
if(newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = x; //将x值赋给newnode
newnode->next = NULL;
//无哨兵位,则需要检验队列中是否为空
//若不为空
if(pq->tail == NULL)
{
pq->tail->next = newnode;//将元素放在队尾
pq->tail = newnode; //将队尾变作新插入的元素
}
//若为空
else
{
pq->head = pq->tail = newnode;
}
pq->size++;
}
3.出队列
void QuePop(Queue* pq)
{
assert(pq);
//当队列中没有节点时,就会强制返回
assert(pq->head != NULL);
QNode* cur = pq->head;
//这里需要分情况讨论,若只有一个节点,tail和head会指向同一个节点
//若有多个节点,tail和head则会分别只想不同的节点,所以需要分开讨论
//若只有一个节点
if(pq->tail->next == NULL)
{
free(pq->tail);
pq->tail = pq->head = NULL;
}
//若有多个节点
else
{
QNode* next = cur->next;//next指向cur的下一个节点,以方便下次删除找到队头
free(pq->head);
cur = next;
}
pq->size--;
}
出队列需要注意的几点:
(1)当队列中有元素时才可以进行删除操作,所以要进行判空(assert(pq->head != NULL))
(2)当队列中只有一个节点时,如果只把tail释放掉是不对的,因为tail和head指向同一个节点,释放掉tail,head就变成了野指针,导致严重错误
(3)当队列中有多个节点,head和tail指向不同的节点,才可以正常删除数据
只有一个节点的情况:
有多个节点的情况:
4.获取队头数据
QDatatype QueFront(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
return pq->head->val;//直接返回队头节点的数据值
}
5.获取队尾数据
QDatatype QueBack(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
return pq->tail->val; //获取队尾数据值
}
6.判空操作
bool QueEmpty(Queue* pq)
{
assert(pq);
//判断size是否为空来返回,若为空,则返回true;若非空,则返回false
return pq->size == 0;
}
7.获取队列节点个数
int QueSize(Queue* pq)
{
assert(pq);
//在此不需要循环队列,直接返回,因为size标记了队列中的节点个数
return pq->size;
}
不需要循环队列,因此时间复杂度为O(1),如果遍历队列链表,时间复杂度为O(N),相比之下,相当于优化了结构的性能
8.销毁队列
void QueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* Next = cur->next; //Next用于迭代队头
free(cur);
cur = Next;
}
pq->head = pq->tail = NULL;//指针都置为空
pq->size = 0;
}
3、源码呈现
3.1Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int QDatatype;
typedef struct QueueNode
{
QDatatype val;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
int size;
}Queue;
//初始化
void QueInit(Queue* pq);
//销毁
void QueDestroy(Queue* pq);
void QuePush(Queue* pq, QDatatype x);
void QuePop(Queue* pq);
bool QueEmpty(Queue* pq);
int QueSize(Queue* pq);
QDatatype QueFront(Queue* pq);
QDatatype QueBack(Queue* pq);
3.2Queue.c
#include"Queue.h"
void QueInit(Queue* pq)
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
pq->size = 0;
}
void QueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* Next = cur->next;
free(cur);
cur = Next;
}
pq->head = pq->tail = NULL;
pq->size = 0;
}
//入队列
void QuePush(Queue* pq, QDatatype x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->val = x;
newnode->next = NULL;
//不带哨兵位,需要进行检验
//如果pq->tail不为空
if (pq->tail)
{
pq->tail->next = newnode;
pq->tail = newnode;
}
//如果为空
else
{
pq->head = pq->tail = newnode;
}
pq->size++;
}
//出队列
void QuePop(Queue* pq)
{
assert(pq);
//零个节点
assert(pq->head != NULL);
//一个节点
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--;
}
QDatatype QueFront(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
return pq->head->val;
}
QDatatype QueBack(Queue* pq)
{
assert(pq);
assert(pq->head != NULL);
return pq->tail->val;
}
int QueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
bool QueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
3.3Test.c
#include"Queue.h"
int main()
{
Queue q;
QueInit(&q);
QuePush(&q, 1);
QuePush(&q, 2);
QuePush(&q, 3);
QuePush(&q, 4);
//printf("%d ", QueFront(&q));
while (!QueEmpty(&q))
{
printf("%d ", QueFront(&q));
QuePop(&q);
}
QueDestroy(&q);
return 0;
}
码字不易,来个一键三连呗~~