1 队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
队列在日常生活中的应用主要就是公平,比如说现在手机上面点餐排队的底层逻辑就是队列,满足先进先出这样一个概念。
2 队列的实现
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据,效率会比较低。
要注意的是,队列怎么进入的,出队列也只有一种顺序,即入队和出队的关系为1:1。
以vs2022为例,这里创建了一个 Queue.h 的头文件和 Queue.c (主要就是将各种队列实现的函数给写进去) 以及一个测试文件 test.c (用于测试代码是否能运行以及后续优化,方便观察)。
下面就是队列的一些基本操作:
typedef int QNDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
struct QueueNode* Next;
QDataType val;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* phead;
QNode* ptail;
}Queue;
// 初始化队列
void QueueInit(Queue* pq);
// 队尾入队列
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);
在单链表的时候,在指针传址说过,需要用到二级指针,因为使用起来比较麻烦,在队列这里正好传一个头指针和一个尾指针就能十分便利的完成队列的要求,有对头指针就能完成出队列的要求,有队尾指针就能完成入队列的要求。(如果不清楚为什么使用二级指针的小伙伴们,可以去主页单链表那篇博客看看,里面有详细说明)
所以如果在这里仍然使用二级指针的话,就比较麻烦,但我们知道,当有两个即两个以上的变量时,我们就可以采用结构体来存放我们所需要的数据,因此这里我们就重新创建了一个 Struct Queue 这样的一个结构体用来存放我们对头指针和队尾指针。
2.1 队列的初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
这里的 size 为了方便后续得到队列元素个数以及队列判空等等,都可以使用上,当然可以根据个人需要,添加一些能用上的参数。
2.2 队列的销毁
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
2.3 入队列
void QueuePush(Queue* pq, QNDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail!");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->phead == NULL)
{
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
2.4 队头删除
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--;
}
在队头删除的时候一定要注意判断元素个数是否为空,为空就没有元素,断言 assert 就会报错。
是否有家人们会有这样的疑问,这里不是队列吗?为什么还会删除(增删改查),删除感觉用不上,或者说是没有必要删除呢。答案肯定是用的上的,这里头删主要是为了后续,出队列做准备。配合取队头函数,判空函数以及队头删除就可以完成出队元素顺序的打印。
2.5 队列元素个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
2.6 获取队头队尾元素
QNDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
return pq->phead->val;
}
QNDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
2.7 队列判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
如果 ps->size == 0 就为空,为空就为真;不为空就为假。
3 队列的打印
#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;
}
以上就是队列的一些常见操作。后续将为大家带来栈和队列的简单oj算法题说明,来进一步理解栈和队列。