[Basic Data Structure] 6. Queue in linear table (chain structure implements queue)

=========================================================================

Relevant code can be obtained from gitee :

C language learning diary: keep working hard (gitee.com)

 =========================================================================

Continuing from the previous issue :

[Elementary Data Structure] 5. Stack in Linear Table (Sequential Table Implements Stack)_Gaogao Fatty’s Blog-CSDN Blog

 =========================================================================

                     

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;
}

Guess you like

Origin blog.csdn.net/weixin_63176266/article/details/133191346