引言
一张图先来看看什么是队列:
队列只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特点。
入队列:进行插入操作,即尾插。
出队列:进行删除操作,即头删。
因为要进行头头插,所以本篇选用无头单向非循环链表(以下简称单链表)作为队列。其中用到的单链表相关知识可以参考我之前的博客:【线性表】单链表详解(C语言版)_是兰兰呀~的博客-CSDN博客
一、定义
既然要用单链表作为队列,那么首先要像单链表那样定义节点,具体操作参考前面链接,在此不再赘述。
有了节点其实还不够,对于队列,我们经常会对其进行插入,即链表的尾插,很显然,如果每次尾插都遍历一下链表,那将会特别麻烦,而且浪费时间,所以咱不妨再定义一个尾指针tail。为了避免每次调用函数不仅要传头指针head还要传尾指针tail,并且插入还得传head的二级指针,我们不妨再将链表的头指针head和链表的尾指针tail封装到一个结构体中,当然这个结构体里面还可以再加上一个size表示该队列长度,每次插入size++,每次删除size--,也免得计算队列长度还需要遍历链表。
由此,我们就有了如下图所示的结构体:
每个QNode节点都是一个结构体,该结构体包含数据data和指向下一结构体的指针next。head和tail是节点结构体类型的指针,分别指向链表的头节点和尾节点。size表示链表,即队列长度。
OK,看代码:
//节点定义
typedef int QDataType;//定义QDataType为int,方便以后修改存储的数据类型
typedef struct QueueNode
{
QDataType data;//节点数据
struct QueueNode* next;//指向下一个节点的指针
}QNode;
//队列定义
typedef struct Queue
{
QNode* head;//头指针
QNode* tail;//尾指针
int size;//队列长度
}Queue;
二、初始化
在初始化中,我们要处理的只有Queue,因为在主函数中,我们只是用“Queue qu;”这条语句创建了一个Queue的结构体qu,此时qu->head、qu->tail、qu->size均为随机值,对于QNode,并没有开辟任何空间,所以也不需要处理。
这样一来,很显然了,我们只需对qu初始化就好,将这个结构体的地址传过去(因为我们要改变qu内的数据),指针置空,其余置0,嗯,完美。
void QInit(Queue* qu)
{
assert(qu);//qu不为空
qu->head = NULL;//置空头指针
qu->tail = NULL;//置空尾指针
qu->size = 0;//队列长度为0
}
三、销毁
销毁最主要一点就是得考到队列里有数据的情况,我们得挨个释放所有节点。
定义两个指针,cur用来释放当前节点,next用来记录释放节点的下一个节点,防止释放后链表的丢失。
释放cur指向节点,注意,释放之后,cur和head均为野指针。
令cur等于next,开始新的循环。
直到cur == NULL,循环终止。 注意,此时tail指针也为野指针。
修改head和tail为空指针,size为0。
看看代码:
void QDestroy(Queue* qu)
{
assert(qu);//qu不为空
QNode* cur = qu->head;//cur遍历队列释放每个节点空间
while (cur)
{
QNode* next = cur->next;//next记录要删除节点的下一个
free(cur);//释放当前空间
cur = next;//指针后移,方便删除下一个
}
qu->head = NULL;//置空头指针
qu->tail = NULL;//置空尾指针
qu->size = 0;//队列长度为0
}
四、插入
因为没有头插、尾插、中间插入之类的乱七八糟的插入,我们省去了链表中开辟节点的函数,将其直接并到了插入函数中。
前面讲过吧,队列的插入就是链表的尾插,我们直接回忆一下单链表是怎么开辟节点和尾插的:
1.开辟节点;2.修改新节点的数据和指针;3.讨论链表是否为空;4.为空令头指针指向新结点;5.不为空尾插入链表。
队列无非是在此基础上增加一个修改尾指针就可以了。
开辟新节点。
修改新节点的数据和指针。
若链表为空,修改头指针和尾指针。
若链表不为空,尾插入链表并修改尾指针。
修改size。
void QPush(Queue* qu, QDataType x)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));//为新节点开辟空间
if (newnode == NULL)//开辟失败
{
perror("QPush");//打印开辟失败
exit(-1);//程序以非正常形式结束
}
//开辟成功
newnode->data = x;//新节点数据为x
newnode->next = NULL;//新节点的next为空
if (qu->tail == NULL)//队列中无元素
{
qu->head = newnode;//修改头指针,指向新节点
qu->tail = newnode;//修改尾指针,指向新节点
}
else//队列有元素
{
qu->tail->next = newnode;//将新节点尾插入链表
qu->tail = newnode;//修改尾指针指向
}
qu->size++;//队列长度加1
}
五、删除
队列的删除其实就是链表的头删。
headnext记录头节点的后一个节点。
释放头指针,将head指向headnext。
修改size。
若队列只有一个节点。
和前面一样,head指向headnext,并修改size。
与前面不同的是,若只有一个节点,需修改tail指针为空。
void QPop(Queue* qu)
{
assert(qu);//qu不为空
assert(!QEmpty(qu));//队列不为空
QNode* headnext = qu->head->next;//next记录要删除节点的下一个
free(qu->head);//释放头节点
qu->head = headnext;//原先的第二个节点变为新的头节点
qu->size--;//队列长度减1
if (qu->head == NULL)//队列删完了
{
qu->tail = NULL;//将尾指针置空,避免变成野指针
}
}
六、取队头数据
取对头元素只需要返回队列第一个节点的数据就可以了。
QDataType QFront(Queue* qu)
{
assert(qu);//qu不为空
assert(!QEmpty(qu));//队列不为空
return qu->head->data;//返回头节点数据
}
七、取队尾数据
这个返回最后一个节点的数据就好。
QDataType QBack(Queue* qu)
{
assert(qu);//qu不为空
assert(!QEmpty(qu));//队列不为空
return qu->tail->data;//返回尾节点数据
}
八、判空
这个函数的返回值我们设为bool类型,空返回真,非空返回假。
bool QEmpty(Queue* qu)
{
assert(qu);//qu不为空
return qu->head == NULL;//返回头指针为空指针的真值
}
九、队列长度
因为前面封装结构体时咱定了一个size,所以这一步就简单了,没必要遍历队列,直接返回size就OK。
int QSize(Queue* qu)
{
assert(qu);//qu不为空
return qu->size;
}
结尾
本篇代码码云指路:class_c: 课上要认真鸭~ - Gitee.com
若有错误,欢迎大家批评斧正!