本篇总结队列是如何实现的,以及各方面的细节问题,比较链表与队列之间的不同,注意到不同的类型,要按照不同需求来定义结构,不能一成不变,印象刻板。
【队列】
⏰.队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特点。
入队列:进行插入操作的一端叫做队尾
出队列:进行删除操作的一端叫队头
⏰.队列的实现
队列可以用数组和链表来实现。但使用链表的结构更优一些,因为如果使用数组结构,删除数据需要挪动大量数据,效率比较低。
队列我们使用链表来实现
首先使用链表定义一个结点
我们都知道结点是由一个结构体定义的,这个给结构体里有个指向下一个结点地址的next和数据data。
但想一下,队列需要对链表进行尾删,也就是每次都要找到尾结点,将链表的头结点和尾结点传给出队列函数,然后进行出队列操作,这样传参数就传好几个,不如让我们将头结点的地址和尾结点的地址分装成一个新的结构体
想一想为什么这里要给个尾指针过去呢?
如果在实现单链表操作中也给尾指针呢?
单链表中给尾指针能完成操作吗?尾插操作需要尾指针,这个操作可以完成,但尾删呢?尾删操作你给个尾指针就能完成吗?当然不能,还需要尾指针前面的结点地址。传个尾指针过去只能完成部分操作,不能完成全部操作还不如不传,代码挫的一批。
但这里不一样,这里队列有它自己的规则:那就是只在一端进行入队,那就是队尾,也就是说在队尾只进行尾插,不进行尾删,所以这里传尾指针过去是可以完成队列所需要的操作的,
要区别单链表中只传一个头指针,而队列这里要传头指针和尾指针的不同,我们要按照需要来定义不同结构。
#pragma once #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> typedef int QDatatype; typedef struct QNode//定义结点结构 { struct QNode* next; QDatatype data; }QNode; typedef struct Queue//将头指针和尾指针分装起来,传给队列操作函数 { QNode* head;//链表头指针 QNode* tail;//链表尾指针 int size;//大小想一想传过去有什么好处? }Queue; //初始化队列 void QueueInit(Queue* pq); //销毁队列 void QueueDestroy(Queue* pq); //队尾入队列 void QueuePush(Queue* pq,QDatatype x); //队头出队列 void QueuePop(Queue* pq); //获取队列的有效数据的大小 void QueueSize(Queue* pq); //判断队列是否为空,如果为空返回非0,如果为非空返回0 bool QueueEmpty(Queue* pq); //获取队列头部元素 QDatatype QueueFront(Queue* pq); //获取队尾元素 QDatatype QueueBack(Queue* pq);
🕐1.初始化队列
首先对传入的数据进行初始化,我们知道传过来的是个分装好的结构体,里面有链表头指针,尾指针,大小。
所以一开始我们需要将头指针尾指针和大小都置0;
//初始化队列 void QueueInit(Queue* pq) { assert(pq);//首先断言判断是否传错 pq->head = pq->tail = NULL; pq->size = 0; }
🕑2.队尾入队列
队尾入队列,实际上就是链表的尾插。
而这正是这操作我们将链表的尾指针也传过去,就是为了每次尾插不需要找尾,每次插入时更新下尾指针位置即可。
//入队列 void QueuePush(Queue* pq, QDatatype x) { assert(pq);//断言是否传错 //入队,就是尾插一个结点,首先生成一个新结点 QNode* newnode = (QNode*)malloc(sizeof(QNode)); newnode->next = NULL;//需要置空,不然会出现野指针 newnode->data = x;//将数据赋值 if (newnode == NULL)//判断是否开辟成功 { perror("malloc"); } //因为为单链表,不带哨兵头,所以一开始head和tail都为空,第一步直接将结点赋值过去,然后后面才是真正的尾插 if (pq->head == NULL) { pq->head = pq->tail = newnode;//将结点赋值过去 } else { //尾插 pq->tail->next = newnode; pq->tail = newnode;//找尾--更新 } pq->size++;//插入一个数据size就要++ }
🕒3.队头出队列
对头出队列,本质上就是链表的头删,头删就是让头指针不断改变,然后让头指针前面的结点释放。
注意点1:
在删除数据之前我们都要对队列进行检查,看是否队列为空。
注意点2:要注意tail是指向最后一个结点的指针。
当head指向最后一个结点后释放,那么tail指向的空间被释放,那tail就变成野指针了,所以最后需要将tail置NULL。
有两中方式:
第一种:只管头删,最后再将tail置NULL
第二种:一开始就考虑二种情况,最后一个结点之前,和最后一个结点,即分为一个结点和多个结点。
//出队列 void QueuePop(Queue* pq) { assert(pq); //在删除数据之前需要考虑队列是否为空; assert(!pq->head==NULL); QNode* next = pq->head->next;//记录头结点后面的结点 free(pq->head);//是否头节点 pq->head = next;//将头指针重新指向后面的结点 //注意如果最后head和tail指向同一个结点,也就是最后一个结点时,tail会变成野指针。 //方法1: if (pq->head == NULL) { pq->tail = NULL; } pq->size--; }
方法2:
void QueuePop(Queue* pq) { assert(pq); //在删除数据之前需要考虑队列是否为空; assert(!pq->head==NULL); //注意如果最后head和tail指向同一个结点,也就是最后一个结点时,tail会变成野指针。 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--; }
🕓4.获取队列头部元素
获取队头数据,就直接将头结点的数据拿走即可,不过在拿之前需要进行判断队列是否为空,如果为空,那还拿什么
//获取队头数据 QDatatype QueueFront(Queue* pq) { assert(pq);//断言判断是否传错 assert(!QueueEmpty(pq));//判断是否为空 return pq->head->data;//队头数据 }
🕔5.获取队列尾元素
和获取队头数据一样需要判断队列是否为空
//获取队尾数据 QDatatype QueueBack(Queue* pq) { assert(pq);//断言判断是否传错 assert(!QueueEmpty(pq));//判断是否为空 return pq->tail->data;//队尾数据 }
🕕6.获取队列中有效元素的个数
看,最开始的问题即将揭晓,在分装的结构体中加上size的作用是什么?
加上size后我们就可以快速获取队列中有效元素的个数了,如果没有加上,要获取个数,那必须对链表进行遍历,时间复杂度就是O(N),而现在就是O(1)。同样在单链表中我们如果想快速获得链表中有效数据的个数,也可以将节点与size分装成一新的结构体。那样就可以啦。
有的人想在带有哨兵头的链表中快速获取个数的办法是将size放入哨兵头里,想着反正哨兵头也不存储数据,就想着将size放进去,但是可以吗?
当然不可以,我们定义的链表中数据的类型有时是知道的,有时是不知道的,当数据类型是int类型是哨兵头里面可以放size,但当链表使用的数据类型是char? double 类型时,你放进去一个int类型的size进去?可以吗?
void QueueSize(Queue* pq) { assert(pq); return pq->size; }
🕖7.检查队列是否为空
bool QueueEmpty(Queue* pq) { assert(pq); return pq->size == 0; }
🕗8.销毁队列
与头删类似,就是循环删除。当删除为空时,停止
然后将数据都置0
//销毁队列 void QueueDestroy(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; }