=========================================================================
Relevant code can be obtained from gitee :
C language learning diary: keep working hard (gitee.com)
=========================================================================
Continuing from the previous issue :
=========================================================================
1. Queue (Queue)
The concept and structure of queue:
Queue concept
- A queue is a special linear table that only allows insertion of data at one end and deletion of data at the other end.
- Entering the queue - the end that performs the insertion operation is called the tail of the queue - the end that performs the dequeue operation is called the head of the queue
- The data elements in the queue follow
the first-in-first-out ( FIFO - First In First Out ) principle - the element that enters first will come out first
, so it can be applied in fairness queuing ( number lottery machine ), BFS ( breadth-first traversal )
Queue structure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2. Implementation of queue
Queues can be implemented using sequence lists ( arrays) or linked lists ( linked structures ) .
If you use a sequence table, it is relatively simple to enter the queue , but you need to delete and move the data when dequeuing , which is less efficient .
So the following uses a linked list ( linked structure ) to implement the queue - one-way + headless + acyclic linked list
Entering the queue - inserting at the end of the singly linked list ( tail insertion ); dequeuing - deleting the head of the singly linked list ( head deletion )
(Details are explained in the comments of the picture, and the code is divided into files under the next title)
Preparatory work before implementing specific functions
- Define the data type stored in the data field in the queue ( chain structure )
- Define queue ( chain structure ) node type
, including queue pointer field and queue data field
- Define the queue type
, including the head node pointer , tail node pointer and the number of queue nodes ( elements )Illustration
---------------------------------------------------------------------------------------------
QueueInit function--initialize the queue
- assert asserts that the queue type pointer is not null
- Set the head node of the queue to empty.
Set the tail node of the queue to empty . Set the number of
queue nodes ( elements ) to 0.Illustration
---------------------------------------------------------------------------------------------
QueueDestroy function--destroy the queue
- assert asserts that the queue type pointer is not null
- Create a pointer cur that traverses the queue.
Use a while loop to traverse and release the queue node.
- After all nodes are released , set the head and tail pointers of the team to empty.
- Then set the number of queue nodes ( elements ) to 0
Illustration
---------------------------------------------------------------------------------------------
QueuePush function--Using the tail insertion operation of the linked list to implement enqueue
- assert asserts that the queue type pointer is not null
- Allocate dynamic space for queue nodes and check the space allocation status
- After the node is successfully opened,
assign the tail interpolation value ( x ) to the data field of the queue node and set the pointer field to empty.
- After the space is opened, tail insertion is performed.
If the queue is empty when the queue is just initialized , assign the newnode address of the newly opened node to the head and tail node pointers.
The queue is not empty , and the tail insertion operation is performed normally.
- Number of queue nodes ( elements ) after inserting data ++
Illustration
---------------------------------------------------------------------------------------------
QueuePop function--implement dequeue using the head deletion operation of the linked list
- assert asserts that the queue type pointer is not empty and the queue is not empty
- There are two situations when dequeuing ( head deletion ) : there is only one node left in the queue - after , the head pointer moves , and the tail pointer also moves to more than one node in the queue - after head deletion , only the head node of the queue needs to be moved
- The number of queue nodes ( elements ) after " deletion " --
Illustration
---------------------------------------------------------------------------------------------
QueueFront function--returns the data field data of the queue head node
- assert asserts that the queue type pointer is not empty and the queue is not empty
- If there is data in the queue , the data in the data field of the head node of the queue will be returned directly.
Illustration
---------------------------------------------------------------------------------------------
QueueBack function--returns the data field data of the end node of the queue
- assert asserts that the queue type pointer is not empty and the queue is not empty
- If there is data in the queue , the data in the data field of the end node of the queue will be returned directly.
Illustration
---------------------------------------------------------------------------------------------
QueueEmpty function--determines whether the queue is empty
- assert asserts that the queue type pointer is not null
- Directly determine whether the next node pointed to by the head node is empty , and directly return the judgment result.
Illustration
---------------------------------------------------------------------------------------------
QueueSize function--determine the number of queue nodes (elements)
- assert asserts that the queue type pointer is not null
- Directly returns the number of size queue nodes ( elements )
Illustration
---------------------------------------------------------------------------------------------
Overall test:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3. Corresponding code
Queue.h -- Queue header file
#pragma once //包含之后需要的头文件: #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <stdbool.h> //以链表(链式结构)实现队列: //双向+循环 的链表可以解决找尾结点的问题, //定义一个尾指针也可以解决该问题, //哨兵位 可以解决二级指针的问题, //且尾插时可以少一层判断,但还有方法可以解决 //定义队列(链式结构)中数据域存储的数据类型: typedef int QDataType; //定义队列(链式结构)结点类型: typedef struct QueueNode { //队列指针域: struct QueueNode* next; //队列数据域: QDataType data; }QNode; //将类型重命名为Qnode //定义队列类型: typedef struct Queue { //因为用链表尾插实现入队, //用链表头删实现出队, //那么就需要头结点和尾结点的指针, //所以可以直接将这两个指针封装为一个类型, //队列类型: //头结点指针: QNode* head; //尾结点指针: QNode* tail; //记录队列结点(元素)个数: int size; //这样之后在出队和入队操作时, //就不需要用到二级指针, //直接接收这个结构体指针, //通过结构体指针运用结构体里的头尾结点指针, //再用头尾结点指针定义头尾结点 //来实现 二级指针、带哨兵位头结点 和 返回值 的作用 //所以现在已知的通过指针定义结点的方法就有4种: // 1. 结构体包含结点指针 // 2. 二级指针调用结点指针 // 3. 哨兵位头结点指针域next指向结点地址 // 4. 返回值返回改变的结点指针 }Que; //重命名为Que //队列初始化函数 -- 将队列进行初始化 //接收队列类型指针(包含链表头尾结点) void QueueInit(Que* pq); //队列销毁函数 -- 将队列销毁 //接收队列类型指针(包含链表头尾结点) void QueueDestroy(Que* pq); //队列入队函数 -- 用链表的尾插操作实现入队 //接收队列类型指针(包含链表头尾结点) 、尾插值 void QueuePush(Que* pq, QDataType x); //队列出队函数 -- 用链表的头删操作实现出队 //接收队列类型指针(包含链表头尾结点) void QueuePop(Que* pq); //队头函数 -- 返回队头结点的数据域数据 //接收队列类型指针(包含链表头尾结点) QDataType QueueFront(Que* pq); //队尾函数 -- 返回队尾结点的数据域数据 //接收队列类型指针(包含链表头尾结点) QDataType QueueBack(Que* pq); //判空函数 -- 判断队列是否为空 //接收队列类型指针(包含链表头尾结点) bool QueueEmpty(Que* pq); //队列大小函数 -- 判断队列结点(元素)个数 //接收队列类型指针(包含链表头尾结点) int QueueSize(Que* pq);
---------------------------------------------------------------------------------------------
Queue.c -- Queue function implementation file
#define _CRT_SECURE_NO_WARNINGS 1 //包含队列头文件: #include "Queue.h" //队列初始化函数 -- 将队列进行初始化 //接收队列类型指针(包含链表头尾结点) void QueueInit(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //将队头结点置为空: pq->head = NULL; //将队尾结点置为空: pq->tail = NULL; //队列结点(元素)个数置为0: pq->size = 0; } //队列销毁函数 -- 将队列销毁 //接收队列类型指针(包含链表头尾结点) void QueueDestroy(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //释放队列跟单链表的释放一样 //先创建一个在队列进行遍历的指针: QNode* cur = pq->head; //从队头结点开始 //使用while循环进行遍历释放队列结点: while (cur != NULL) { //先保存下个结点: QNode* next = cur->next; //再释放当前结点: free(cur); //再指向下个结点: cur = next; } //结点都释放后,把队头队尾指针都置空: pq->head = NULL; pq->tail = NULL; //再把队列结点(元素)个数置为0: pq->size = 0; } //队列入队函数 -- 用链表的尾插操作实现入队 //接收队列类型指针(包含链表头尾结点) 、尾插值 void QueuePush(Que* pq, QDataType x) { //assert断言队列类型指针不为空: assert(pq != NULL); //入队放入元素需要空间, //所以要先为队列结点开辟动态空间: QNode* newnode = (QNode*)malloc(sizeof(QNode)); //检查是否开辟成功: if (newnode == NULL) { //开辟失败则打印错误信息: perror("malloc fail"); //终止程序: exit(-1); } //队列结点完成后将尾插值(x) //赋给队列结点数据域: newnode->data = x; //指针域指向空: newnode->next = NULL; //空间开辟后进行尾插: if (pq->tail == NULL) //如果队列刚初始化,队列为空, //头结点指针和尾结点指针都为空: { //那么将刚开辟的结点newnode地址 //赋给头结点指针和尾结点指针 pq->head = newnode; pq->tail = newnode; } else //队列不为空,进行尾插: { //将目前队尾结点指针域next指向尾插结点: pq->tail->next = newnode; //然后再指向尾插结点,成为新队尾结点: pq->tail = newnode; } //插入数据后队列结点(元素)个数++: pq->size++; } //队列出队函数 -- 用链表的头删操作实现出队 //接收队列类型指针(包含链表头尾结点) void QueuePop(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //assert断言队列不为空,没数据不能删除: assert(QueueEmpty != true); //不为空就继续程序 //如果队列中只剩一个结点: if (pq->head->next == NULL) //队头指针指向空,说明只剩一个结点, //只剩一个结点说明队头队尾指针都指向这一个结点, //所以这时头删后头指针移动,尾指针也要移动 { //先释放("删除")队列目前头结点: free(pq->head); //删除后将队头队尾指针都置为空: pq->head = NULL; pq->tail = NULL; } else //队列不止一个结点,则头删后只需移动队头结点: { //用链表的头删操作实现出队, //先保存第二个结点地址: QNode* next = pq->head->next; //释放("删除")队列目前头结点: free(pq->head); //再将队头结点指针指向原本第二个结点next, //让其成为新的队头结点: pq->head = next; } //“删除”后队列结点(元素)个数--: pq->size--; } //队头函数 -- 返回队头结点的数据域数据 //接收队列类型指针(包含链表头尾结点) QDataType QueueFront(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //assert断言队列不为空,没数据不能查找: assert(QueueEmpty != true); //不为空就继续程序 //队列有数据,则直接返回队头结点数据域数据: return pq->head->data; } //队尾函数 -- 返回队尾结点的数据域数据 //接收队列类型指针(包含链表头尾结点) QDataType QueueBack(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //assert断言队列不为空,没数据不能查找: assert(QueueEmpty != true); //不为空就继续程序 //队列有数据,则直接返回队尾结点数据域数据: return pq->tail->data; } //判空函数 -- 判断队列是否为空 //接收队列类型指针(包含链表头尾结点) bool QueueEmpty(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //直接判断队头结点指向的下个结点是否为空: return pq->head == NULL; //是则返回true -- 队列为空 //是则返回false -- 队列不为空 } //队列大小函数 -- 判断队列结点(元素)个数 //接收队列类型指针(包含链表头尾结点) int QueueSize(Que* pq) { //assert断言队列类型指针不为空: assert(pq != NULL); //直接返回size队列结点(元素)个数: return pq->size; }
---------------------------------------------------------------------------------------------
Test.c -- Queue test file
#define _CRT_SECURE_NO_WARNINGS 1 //包含队列头文件: #include "Queue.h" //队列测试函数: void TestQueue() { //创建队列类型: Que q; //对队列类型进行初始化: QueueInit(&q); //进行入队操作: QueuePush(&q, 1); QueuePush(&q, 2); QueuePush(&q, 3); QueuePush(&q, 4); QueuePush(&q, 5); //当前队尾值: printf("当前队尾值:%d\n", QueueBack(&q)); //当前队列元素个数: printf("当前队列元素个数:%d\n", QueueSize(&q)); //换行: printf("\n"); //使用while循环遍历进行出队: //(类似抽号机,当前号抽完就到下个号) while (!QueueEmpty(&q)) //队列不为空就继续出队: { //打印出队值: printf("当前出队值为:%d\n", QueueFront(&q)); //进行出队: QueuePop(&q); //出队后打印下个出队值 } //换行: printf("\n"); //当前队列元素个数: printf("当前队列元素个数:%d", QueueSize(&q)); //销毁队列: QueueDestroy(&q); } //主函数: int main() { //调用队列测试函数: TestQueue(); return 0; }