队列 --- Queue
- 前言
- 一、整体结构
- 二、相关方法
-
- 1.初始化
- 2.销毁
- [3.入队 - - - 队尾插插入数据](#3.入队 - - - 队尾插插入数据)
- [4.出队 - - - 队头删除数据](#4.出队 - - - 队头删除数据)
- [5.获取队头 / 队尾元素](#5.获取队头 / 队尾元素)
- 6.判空
- 7.获取队列有效元素个数
前言
队列是一种特殊的线性表,其特性是先进先出,只能在队尾入数据,在队头出数据,本次实现使用链表结构实现队列,对比数组实现有下列优点:
尽管现在来看两者的时间复杂度是差不多的,但是对于链表来说,我们只需要再定义一个尾节点来表示队尾即可将入队操作的时间复杂度优化为O(1),而数组的出队操作是无法优化时间复杂度的,所以综上所述本次实现选择链表结构实现队列。故而逻辑结构上是线性的,物理结构上不一定是线性的。
本次实现使用C语言实现。
一、整体结构
本次实现队列底层结构是链表,所以说有一个节点结构类型,并且队列操作数据只有队头和队尾位置可以操作,所以将其抽离出来,这就是队列的结构。
节点的结构:
c
typedef int QDataType;
// 队列的节点结构
typedef struct QueueNode
{
QDataType data; // 数据域
struct QueueNode* next; // 指针域
}QueueNode;
队列的结构:
c
// 队列的结构
typedef struct Queue
{
QueueNode* phead; // 队头
QueueNode* ptail; // 队尾
// int size; // 有效数据个数
}Queue;
二、相关方法
1.初始化
初始化即将队列phead和ptail置为空即可。
c
// 初始化
void QueueInit(Queue* pq)
{
// pq不能为空
assert(pq);
// 初始化成空
pq->phead = pq->ptail = NULL;
// pq->size = 0;
}
创建一个空队列q1并初始化它,
监视窗口演示:
2.销毁
常见的标记销毁位置起始的节点,并记录此位置的下一个节点,之后循环销毁节点,完成后将phead和ptail置为NULL。
c
// 销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
// 标记队头
QueueNode* pcur = pq->phead;
// 销毁节点
while (pcur)
{
// 记录pcur的下一个节点
QueueNode* pnext = pcur->next;
free(pcur);
pcur = pnext;
}
// 销毁完成后phead和ptail置为NULL
pq->phead = pq->ptail = NULL;
// pq->size =0;
}
3.入队 - - - 队尾插插入数据
入队两步骤:
1)申请新节点,动态开辟(malloc)一个节点大小的内存空间,更新数据域为入队元素x,再将指针域置为NULL。
2)判断队列是否有元素,若有元素则直接在队尾ptail后面插入数据,并更新队尾指针指向;若没有元素,则新插入的节点newnode既是队头phead也是队尾ptail。
c
// 入队 --- 队尾入
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
// 申请新节点
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc");
exit(1);
}
newnode->data = x;
newnode->next = NULL;
// 当队列为空时
if (pq->phead == NULL)
{
// 新节点就是队头和队尾
pq->phead = pq->ptail = newnode;
}
// 当队列不为空时
else
{
// 队尾入队列
pq->ptail->next = newnode;
// 移动队尾指针
pq->ptail = pq->ptail->next;
}
// pq->size++;
}
向初始化完成后的队列q1入队三个数据:10,20,30
监视窗口演示:
4.出队 - - - 队头删除数据
出队两步骤:
1)判断队列是否是非空状态,只有有元素才可以出队。
2)判断队列的节点个数,当只有一个节点时,销毁此节点并将phead和ptail置为NULL;当有多个节点时,也是销毁此节点,并更新队头指针。
c
// 出队 --- 队头出
void QueuePop(Queue* pq)
{
// 队列非空才可以出队
assert(!QueueEmpty(pq));
// 当队列只有一个节点
if (pq->phead == pq->ptail)
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
// 当队列有多个节点时
else
{
// 标记队头的下一个节点
QueueNode* pnext = pq->phead->next;
free(pq->phead);
// 移动队头至下一个节点
pq->phead = pnext;
}
//pq->size--;
}
向入队完成后的队列q1出队前两个数据:10,20
监视窗口演示:
5.获取队头 / 队尾元素
获取两者的元素各自访问队头队尾指针的数据域即可,前提是队列非空。
获取队头元素:
c
// 获取队头元素
QDataType QueueFront(Queue* pq)
{
// 队列非空才可以获取
assert(!QueueEmpty(pq));
return pq->phead->data;
}
获取队尾元素:
c
// 获取队尾元素
QDataType QueueBack(Queue* pq)
{
// 队列非空才可以获取
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
6.判空
队列为空的条件是队头指针或者队尾指针等于NULL。
c
// 判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
7.获取队列有效元素个数
有两种方法:
1)遍历队列,定义一个计数器size,每访问一个节点计数器加一。
2)在队列属性里多增加一个size属性,直接返回size即可,增加这一个属性之后,初始化,入队,出队,销毁这种需要改变size大小的方法也需要对size进行操作。
c
// 获取队列有效元素个数
int Queuesize(Queue* pq)
{
// 方法一:遍历,统计
// 适用于非频繁访问有效数据个数的场景 : O(N)
// 标记队头
QueueNode* pcur = pq->phead;
int size = 0;
while (pcur)
{
size++;
pcur = pcur->next;
}
return size;
// 方法二:给队列增加一个size属性
// 适用于频繁访问有效数据个数的场景 :O(1)
//return pq->size;
}