大家好呀,我们今天就来认识一下队列。
栈与队列
目录
队列
那么什么是队列呢?
队列就是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头,如图:
队列的实际应用就有很多了,比如医院取票,吃饭排队等待。那么我们今天就来实现一下队列。
创建队列
队列是尾进头出,相当于尾插和头删。那么我们该选择怎么来实现队列呢?是双向循环链表,是单链表,还是数组?
这里我们选择使用单链表,数组的头删会很麻烦,双向循环链表固然是可以,但是它相比与单链表多出了一个指针。而只用尾插和头删的单链表,在这里的优势就大大的显示出来了。
同样的,我们选择用结构体来存其元素。代码如下:
typedef struct QueueNode { QDataType val; struct QDataType* next; }QNode;
入队和出队
在编写之前,我们先来考虑传参的问题。
入队和出队相当于单链表的尾插和头删。那么单链表实现尾插和头删,那么一定会传头节点过去。但是尾插的话,如果有尾节点,那么实现尾插就会方便很多,因此我们就会考虑将尾节点传过去。
但是,若是将尾节点传过去的话,找到尾节点并将其改变就需要二级指针,那么实际写代码就会麻烦不少。所以我们就将头节点和尾节点用结构体封装起来,通过结构体对其实现调用。代码如下:
typedef struct Queue { QNode* phead; QNode* ptail; int size;//方便计算队列长度 }Queue;
接下来正式开始编写入队。
入队相当于单链表的尾插,使用malloc创建新节点。让尾指针指向它,新节点指向空,最后改变尾指针位置即可。代码如下:
void QueuePush(Queue* pq, QDataType x) { assert(pq); QNode* newnode = (QNode*)malloc(sizeof(QNode)); if (newnode == NULL) { perror("malloc fail"); exit(-1); } newnode->val = x; newnode->next = NULL; if (pq->ptail == NULL) { pq->ptail = pq->phead = newnode; } else { pq->ptail->next = newnode; pq->ptail = newnode; } pq->size++; }
接下来编写出队。
出队,也就是单链表的头删。那我们就可以直接记录当前头节点,让头节点指向下一个后将记录节点进行释放即可。但是这样就会产生一个问题,当队列为空或者队列里面有一个值的时候,我们让phead往后移动,就会出现野指针的情况,因此需要进行判断。代码如下:
void QueuePop(Queue* pq) { assert(pq); assert(pq->phead); QNode* del = pq->phead; pq->phead = pq->phead->next; free(del); del = NULL; if (pq->phead == NULL) { pq->ptail = NULL; } pq->size--; }
初始化和销毁
进行初始化和销毁已经是老传统了,我们进行编写。
首先便是初始化,将值赋为0,将指针置空就可以了。代码如下:
void QueueInit(Queue* pq) { assert(pq); pq->phead = pq->ptail = NULL; pq->size = 0; }
接着便是销毁函数。
销毁函数的话就需要将队列的每个元素就删除,这里我们使用循环进行删除,保存当前节点,指向下个节点,进行释放,指针改变位置就可以了。代码如下:
void QueueDestroy(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; }
取队头,队尾数据
取队头队尾数据就比较简单了。直接通过指针获取即可,最后将其返回。
取队头数据,代码如下:
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; }
检查队列是否为空
那么如何来检查队列是否为空呢?
那我们就来看看队头队尾两个指针,那么当队头为空的时候,那么phead->val就一定没有数据。同样的,若是队尾的指针指向空,也是一定没有数据的,那么判定是否为空的标准就可以以队头队尾指针是否为空来进行判断。代码如下:
bool QueueEmpty(Queue* pq) { assert(pq); return pq->phead == NULL; }
计算队列长度
那么这时候我们在结构体里面放了个size的优势就体现出来了,之前我们在入队的时候,每入一次队,就让size++,出队,让size--。这里就无需再进行遍历了,直接返回size即可。代码如下:
int QueueSize(Queue* pq) { assert(pq); return pq->size; }
检查打印
当然,我们最后还是需要进行验证。我们让1,2,3,4,5依次入队。用while循环来进行打印。代码如下:
int main() { Queue q; QueueInit(&q); QueuePush(&q, 1); QueuePush(&q, 2); QueuePush(&q, 3); QueuePush(&q, 4); QueuePush(&q, 5); while (!QueueEmpty(&q)) { printf("%d ", QueueFront(&q)); QueuePop(&q); } printf("\n"); return 0; }
运行结果如下:
完整代码
Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType val;
struct QDataType* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;//方便计算队列长度
}Queue;
//入队
void QueuePush(Queue* pq, QDataType x);
//出队
void QueuePop(Queue* pq);
//初始化
void QueueInit(Queue* pq);
//销毁
void QueueDestroy(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//检查队列是否为空
bool QueueEmpty(Queue* pq);
//计算队列长度
int QueueSize(Queue* pq);
Queue.c
#include"Queue.h"
//初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
//销毁
void QueueDestroy(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;
}
//入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->val = x;
newnode->next = NULL;
if (pq->ptail == NULL)
{
pq->ptail = pq->phead = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
//出队
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->phead);
QNode* del = pq->phead;
pq->phead = pq->phead->next;
free(del);
del = NULL;
if (pq->phead == NULL)
{
pq->ptail = NULL;
}
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;
}
//检查队列是否为空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->phead == NULL;
}
//计算队列长度
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
test.c
#include"Queue.h"
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
return 0;
}
小结
对栈和队列的认识到这里就结束啦,大家继续加油,冲冲冲!我们下次见!