【数据结构】队列

队列

  • 1、队列的概念及结构
  • 2、队列的实现
    • 2.1 定义队列
    • 2.2 队列初始化
    • 2.3 队列销毁
    • 2.3 队列插入
    • 2.4 判空函数
    • 2.5 出数据
    • 2.6 取头部的数据
    • 2.7 取尾部的数据
    • 2.8 计算队列的大小
  • 3、实现队列的全部代码

1、队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为对头
【数据结构】队列_第1张图片

2、队列的实现

队列可以通过数组或者链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列是在数组头上出数据,效率会比较低。
【数据结构】队列_第2张图片

2.1 定义队列

使用链表来定义队列,这个链表由两个结构体定义,其中一个结构体中的成员包括存储的数据和下一个结构体的地址,另一个结构体中的成员包含对头和队尾的地址。如下:

//队列用单链表实现
typedef int QDataType;
typedef struct	QueueNode
{
	struct	QueueNode* next;
	QDataType data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

2.2 队列初始化

//队列初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

队列在还没有存储数据的时候,即没有队列,对头跟队尾指向空。

2.3 队列销毁

//队列销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
	}

	pq->head = pq->tail = NULL;
}
  1. 队列是先进先出的,出队列的一端是对头,所以销毁队列的时候应该从对头开始销毁。
  2. 当队列中的所有数据都清空时,队列回到初始化状态,将对头与队尾指向空。

2.3 队列插入

//队列插入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}

	if (pq->tail == NULL)//说明此时队列中还没有数据,此时插入进来的是第一个数据
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode; //对于链表来说,此时新插入进来的数据变成尾部
	}
}
  1. 使用链表存储数据,不会造成空间多余,所以当要插入数据时要先进行动态内存开辟空间,申请一个QNode结构体大小的空间用来存放要插入的数据。
  2. 如果尾节点指向的是空节点(也可以说头节点指向的是空节点),说明对列中还没有存储数据,此时新插入进来的数据为第一个数据,对头与队尾都指向这个新插入进来的数据。
  3. 如果队列中已经有多个数据了,将新插入进来的节点与尾节点相连接,然后将这个新插入的节点作为新的尾节点。

2.4 判空函数

//判空函数
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL && pq->tail == NULL;
}
  1. 对头与队尾都指向空时,说明队列中没有存储数据,队列为空。
  2. 队列为空时返回值为真,队列不为空时返回值为假

2.5 出数据

//出数据(因为队列是先进先出,所以出的是头部的数据)
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //保证队列不能为空

	if (pq->head->next == NULL) //说明只剩最后一个节点了
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;
		free(del);
		del = NULL;
	}
	
}
  1. 队列出数据要先保证队列中已经存储了数据,所以队列在出数据之前要借助判空函数断言一下。
  2. 如果对头的下一个节点为空,说明队列中只剩一个数据,此时直接将这个节点free掉,然后将头节点与尾节点指向的地址置为空。
  3. 队列先进先出,队列出数据从对头开始出,直接将对头指向的下一个节点作为新的对头,并且将之前的对头指向的结构体free掉并且置空。

2.6 取头部的数据

//取头部的数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //保证队列不能为空

	return pq->head->data;
}
  1. 取数据之前先判空
  2. 直接将头节点中的数据返回。

在主函数中调用

void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");

	QueueDestroy(&q);
}

int main()
{
	TestQueue();
	return 0;
}  

【数据结构】队列_第3张图片

  1. 通过数据插入将1 2 3 4依次插入到队列中,然后通过取头部数据与出数据将队列中的数据打印了出来。

2.7 取尾部的数据

//取尾部的数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //保证队列不能为空

	return pq->tail->data;
}
  1. 取数据之前先判空
  2. 直接将尾部节点中的数据返回。

2.8 计算队列的大小

//计算队列的大小
int QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	int n = 0;
	while (cur)
	{
		++n;
		cur = cur->next;
	}

	return n;
}
  1. 从头节点开始,每遍历一个节点,计数一次,直到尾节点。

3、实现队列的全部代码

Queue.h文件:写函数的声明,函数的头文件,其他 .c文件只要包含Queue.h文件——>#include “Queue.h”,就相当于写了函数的声明与头文件。如下:

#pragma once
#include 
#include 
#include 
#include 

//队列用单链表实现
typedef int QDataType;
typedef struct	QueueNode
{
	struct	QueueNode* next;
	QDataType data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

//队列初始化
void QueueInit(Queue* pq);

//队列销毁
void QueueDestroy(Queue* pq);

//队列插入
void QueuePush(Queue* pq, QDataType x);

//出数据(因为队列是先进先出,所以出的是头部的数据)
void QueuePop(Queue* pq);

//取头部的数据
QDataType QueueFront(Queue* pq);

//取尾部的数据
QDataType QueueBack(Queue* pq);

//判空函数
bool QueueEmpty(Queue* pq);

//计算队列的大小
int QueueSize(Queue* pq);

Queue.c文件:用来实现队列的各种功能接口函数。如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"

//队列初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

//队列销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
	}

	pq->head = pq->tail = NULL;
}

//队列插入
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	else
	{
		newnode->data = x;
		newnode->next = NULL;
	}

	if (pq->tail == NULL)//说明此时队列中还没有数据,此时插入进来的是第一个数据
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode; //对于链表来说,此时新插入进来的数据变成尾部
	}
}

//出数据(因为队列是先进先出,所以出的是头部的数据)
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //保证队列不能为空

	if (pq->head->next == NULL) //说明只剩最后一个节点了
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;
		free(del);
		del = NULL;
	}
	
}

//取头部的数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //保证队列不能为空

	return pq->head->data;
}

//取尾部的数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq)); //保证队列不能为空

	return pq->tail->data;
}

//判空函数
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL && pq->tail == NULL;
}

//计算队列的大小
int QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	int n = 0;
	while (cur)
	{
		++n;
		cur = cur->next;
	}

	return n;
}

Test.c文件:主函数写在这,在主函数中可以调用 Queue.c文件中实现的各种接口。如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"

void TestQueue()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);

	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");

	QueueDestroy(&q);
}

int main()
{
	TestQueue();
	return 0;
}  

你可能感兴趣的:(c语言,数据结构,数据结构,算法)