队列的认识
和栈一样,队列同样是一种线性表,特点是只允许在一端进行插入操作(队尾),在另一端进行删除操作(队头)
符合 FIFO(first in first out),先进的先出(队列的核心特点)
入队列:进行插入数据操作的一端为队尾
出队列:进行删除数据操作的一端为队头
队列可以用数组来实现,也可以用链表来实现,显然用链表来实现更优,因为数组插入数据或者删除数据可能需要大量挪动数据
队列的实现
Queue.h
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size;
}Queue;
//队列初始化
void QueueInit(Queue* pq);
//队列销毁
void QueueDestory(Queue* pq);
//队列插入
void QueuePush(Queue* pq, QDataType x);
//出队头数据
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队列的尾
QDataType QueueBack(Queue* pq);
//判断队列是否为空
bool QueueEmpty(Queue* pq);
这里面用了两个结构体,第一个结构体是每个节点的结构,而第二个结构体有指向队列的头尾指针以及当前队列元素个数
Queue.c
1.队列初始化
开始时队列为空,因此我们把头尾指针都复制成NULL,size赋值成0
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
2.队列销毁
①本质就是要释放掉队列所占据的内存空间,而队列是由单链表来实现的,里面是一个个节点,因此我们就用之前讲过的释放单链表的方法来释放队列---即遍历链表,释放一个个节点
②由于队列销毁了,因此也要把phead和ptail都置空,size归0
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;
}
3.队尾入数据
①本质就是单链表的尾插,之前经常把新建节点封装成函数,是因为单链表有头插尾插以及任意位置插入删除都要用到创建节点,而队列的特点是只能从队尾入数据,所以就这一个功能需要用到创建节点,就没必要封装成函数了~
②尾插的时候要分类讨论(已经很常见了~,避免空指针解引用~)
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail\n");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->phead == NULL)
{
assert(pq->ptail == NULL);
pq->phead = pq->ptail = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
解释一下上述代码中的这一步,其实这句断言可以没有,因为理论上来说当phead为NULL时,ptail也一定为空,加上这句话就是保险起见,万一代码或者操作哪里出了问题,导致phead为空,ptail不为空,这里就会报错提示
4.队头出数据
①队列为空,当然不能出数据了,因此记得断言~
②思考一下队列只剩一个节点和还有多个节点时出数据代码一样吗~
我们先考虑队列有多个节点时出数据~,这就是单链表中的头删操作
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
这三句代码就可以实现头删
再考虑队列只剩下一个节点时出数据~
可以看到,当队列只剩下一个节点时候,如果还是上述代码,会出现一个问题:ptail成为了野指针,因为最后一个节点已经被释放了,但是ptail仍然保存了最后一个节点的地址,经典的野指针
所以只有一个节点时队头出数据,直接释放掉最后一个节点,然后让ptail = phead = NULL即可
考虑到这一点,队头出数据应该分类讨论
void QueuePop(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
//一个节点
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--;
}
5.取队头数据
要assert断言队列是否为空
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->phead->data;
}
6.取队尾数据
要assert断言队列是否为空
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->ptail->data;
}
7.求队列长度
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
8.判断队列是否为空
队列为空的特点是size为0 或者 phead 和 ptail都为空
采取一种办法判断即可~
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
//return pq->phead == NULL && pq->ptail == NULL;
}
test.c
#include"Queue.h"
void TestQueue()
{
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");
QueueDestory(&q);
}
int main()
{
TestQueue();
}
队列的基本内容就介绍到这了,欢迎大家交流指正~