本文已收录至《数据结构(C/C++语言)》专栏,欢迎大家 点赞 + 收藏 + 关注 !
目录
前言
正文
队列的数据结构
队列的操作函数
队列的实现
初始化队列
入队函数
出队函数
查询队头数据函数
查询队尾数据函数
查询队列元素个数函数
检查队空函数
链队列销毁函数
总结
大家看到文章的标题,可能会对队列有许多的联想,我们可以想象我们平时上学时大课间站队做操时的情景,先来的人站在最前面,后来的人依次在队尾向后站,数据结构中队列的性质也与此相似,这一节我们就来学习数据结构中的队列!
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头。
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据,效率会比较低。
总而言之,队列的操作在队头和队尾,头出尾进,因此具有先进先出的性质。
队列的数据结构
构建动态链式队列,首先每个节点需要单链表的数据结构,其次我们需要一个头尾指针记录头节点地址和尾节点地址,方便入队和出队,这里的入队和出队类似于头删和尾插,其次我们需要一个整型变量记录队列元素个数。
typedef int QDataType; // 链式结构:表示队列 typedef struct QListNode//单链表结构 { struct QListNode* _next;//后继指针 QDataType _data;//数据域 }QNode; // 队列的结构 typedef struct Queue队列结构 { QNode* _front;//头指针 QNode* _rear;//尾指针 int size;//元素个数 }Queue;
队列的操作函数
既然是链式结构,操作则与有一部分单链表类似,不过由于队列性质的限制,操作非常局限。
/*动态链队列*/ // 初始化队列 void QueueInit(Queue* q); // 队尾入队列 void QueuePush(Queue* q, QDataType data); // 队头出队列 void QueuePop(Queue* q); // 获取队列头部元素 QDataType QueueFront(Queue* q); // 获取队列队尾元素 QDataType QueueBack(Queue* q); // 获取队列中有效元素个数 int QueueSize(Queue* q); // 检测队列是否为空,如果为空返回1,如果非空返回0 int QueueEmpty(Queue* q); // 销毁队列 void QueueDestroy(Queue* q);
队列的实现
初始化队列
对于队列的初始化,我们需要置空头指针和尾指针,防止野指针的访问,其次需要置0记录队列元素个数的变量size。
// 初始化队列 void QueueInit(Queue* q) { assert(q);//检查空指针 q->_front = q->_rear = NULL;//头尾指针置空 q->size = 0;//元素个数置空 }
入队函数
队列的入队在队尾,与单链表的尾插相似,我们只需要申请一个新的节点,然后将新节点的_next指向尾头节点,而原尾节点的地址是_rear指向的地址,我们指向将新节点链接在当前尾节点的后面,然后修改_rear地址的指向,使_rear指向新(尾)节点即可!
但是这里我们需要判断是否是首次入队,如果是首次入队需要将首节点的地址赋给头指针!
// 队尾入队列 void QueuePush(Queue* q, QDataType x) { assert(q);//检查空指针 QNode* newnode = (QNode*)malloc(sizeof(QNode));//申请一个新节点 if (!newnode)//判断是否申请成功 { perror("malloc fail!\n"); exit(-1); } newnode->_data = x;//赋值新节点 newnode->_next = NULL;//先置空新节点的指针 if (!q->_rear)//判断是否为首次插入 { q->_front = q->_rear = newnode;//如果是则直接插入且将节点地址赋给头尾指针 } else { //如果不是则让当前尾节点指向新节点,尾指针指向新(尾)节点 q->_rear->_next = newnode;//尾指针节点的next指向新节点 q->_rear = newnode;//尾指针指向新节点 } ++(q->size);//队列元素个数加1 }
出队函数
队列的出队在队头,与单链表的头删相似,在删除前需要判断队列是否为空,定义一个变量保存即将删除的节点,然后让头指针_front指向原头节点的下一个节点,free释放原头节点的内存空间元素个数_size减一即可。
这里我们要注意的是,我们需要判断删除的是否是队列中最后一个节点,如果是则需要在删除节点后置空头指针_front和尾指针_rear,防止野指针的访问!
// 队头出队列 void QueuePop(Queue* q) { assert(q);//判断指针是否为空 if (!QueueEmpty(q))//判断队列是否为空 { QNode* freenode = q->_front;//存储当前的头节点方便释放 if ((q->_front) != (q->_rear))//判断是否为最后一个节点 { q->_front = q->_front->_next;//头指针指向头节点的下一个 free(freenode);//释放原头节点地址空间 } else//释放最后一个节点 { q->_front = q->_rear = NULL;//置空头尾指针防止野指针访问 free(freenode);//释放最后一个节点的地址空间 } --(q->size);//队列元素个数减一 } }
查询队头数据函数
取队头函数是取头节点的数据,如果头节点存在则函数返回头节点的_data数据,如果不存在则返回-1(或其他提示性反馈)。
// 获取队列头部元素 QDataType QueueFront(Queue* q) { assert(q);//检查空指针 if (!QueueEmpty(q))//判断队列是否为空 { return q->_front->_data;//返回头节点的data数据 } return -1;//为空返回-1(或其他提示性反馈) }
查询队尾数据函数
取队尾数据与取队头一样,尾节点存在则返回尾节点的_data数据,不存在则返回-1(或其他提示性反馈)。
// 获取队列队尾元素 QDataType QueueBack(Queue* q) { assert(q);//判断是否为空指针 if (!QueueEmpty(q))//判断队列是否为空 { return q->_rear->_data;//返回队尾节点的data数据 } return -1;//为空返回-1(或其他提示性反馈) }
查询队列元素个数函数
查询队列元素个数,我们只需要返回记录节点个数的结构体成员size即可。
// 获取队列中有效元素个数 int QueueSize(Queue* q) { assert(q);//检查空指针 return q->size;//返回元素个数 }
检查队空函数
检查队列是否为空我们只需要判断头指针_front和尾指针_rear为NULL且size等于0即可!如果为空则返回1,不为空返回0。
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 int QueueEmpty(Queue* q) { assert(q);//判断空指针 //如果头尾指针都为空且size=0则队列为空,返回1 return ( q ->_front == NULL && q ->_rear == NULL && q->size == 0); }
链队列销毁函数
队列的销毁从队头开始,定义一个变量cur遍历使用free释放每个节点的内存空间,指导cur遍历到队尾节点的_next为NULL,迭代停止,销毁结束!在执行销毁时如果队列已经为空则不需要销毁!
这次我们介绍了特殊线性表之一队列,队列是一种特殊的线性表,队列的操作只能在队头和队尾,队列和栈一样常用于辅助一些其他的数据结构实现一些复杂的功能,看完本节相信大家对队列的操作已经有了一定的了解。
本次队列的基础知识介绍就到这里啦,希望能够尽可能帮助到大家。
如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!
博客中的所有代码合集:链队列
其他文章阅读推荐
数据结构初级<带头双向循环链表>_CSDN博客
数据结构初级<线性表之链表>_CSDN博客
数据结构初级<线性表之顺序表>_CSDN博客
欢迎读者多多浏览多多支持!