目录
一.🍻前言🍻
相信经过前面单链表,双链表,栈等数据结构的学习后,各位对数据结构也有了一定的了解。每种数据结构都有其自身的特点,而创建这些数据结构的目的都是为了更好的管理数据。那么,我们废话不多说,马上进入下一种数据结构(队列)的学习。
二.🍻队列的概念及结构🍻
1.🍺队列的概念🍺
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特性。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
2.🍺队列的结构🍺
三.🍻队列的实现🍻
数组和链表这两种结构都可以实现队列,但是使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。对头出数据即删除对头数据,如果用数组实现,删除对头数据则需要将对头之后的数据全部往前进行覆盖,这样会大大降低程序效率,因此我们选用单链表来实现队列。
若对链表不是很清楚的小伙伴可以到博主主页考一下古,链接:http://t.csdnimg.cn/RGQWQ
1.🍺定义结点及队列结构🍺
//定义类型
//链表队列
//队列特点:先进先出,队尾入数据,队头出数据
typedef int QDataType;
typedef struct QNode
{
QDataType data;
struct QNode* next;
}QNode;
typedef struct Queue
{
QNode* Phead;
QNode* Ptail;
int size;
}Queue;
链表的结点由数据域和指针域爱两个部分构成,这就意味着我们需要对多类数据进行操作,因此定义一个结构体,正好可以满足这一需求。队列在队尾入数据,队头出数据,因此我们单独定义一个头和尾,外加一个记录队列大小的size来定义队列,以便后续实现队列的各种功能。用类型重定义关键字typedef将数据类型以及结构体类型分别重定义为QDataType和QNode以及Queue,以便后续修改代码和操作。
2.🍺队列的初始化🍺
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->Phead = pq->Ptail = NULL;
pq->size = 0;
}
队列的初始化非常简单,用assert对pq断言,pq是在外部定义的队列的地址,断言是为了防止使用者误传空指针。其余将Phead和Ptail置空,size赋初值为0即可。
3.🍺销毁队列🍺
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->Phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->Phead = pq->Ptail = NULL;
pq->size = 0;
}
看过博主写的链表的销毁的小伙伴肯定会发现,链表的销毁和这里队列的销毁是一样的,因为队列就是用链表来实现的,所以,如果你看过链表的实现,那么队列的实现对你来说肯定就是小菜一碟。还是老套路,我们用一个cur指针来遍历一遍队列,然后再用一个指针next来保存cur的下一个位置地址,将cur当前指向空间进行释放,然后再将cur往后走,循环结束后,建立的队列也就销毁了。
4.🍺队尾入数据🍺
//队尾入数据
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->Phead == NULL)
{
pq->Phead = pq->Ptail = newnode;
}
else
{
pq->Ptail->next = newnode;
pq->Ptail = newnode;
}
pq->size++;
}
队尾入数据时,需要先动态开辟出一个新结点,以便存放数据。入数据时需要考虑两个情况,一是队列为空,二是队列里已经存在结点。当Phead为空时,说明此时队列为空,需将新节点作为队列的头结点;当Phead,Ptail不为空后,尾插一个数据,就将Ptail往后走到下一个结点,这样Ptail就永远处在尾结点上,而Phead永远处于头结点。如此,获取对头数据以及队尾数据就将极为方便。
队尾数据插入成功后,需将size累加一下,以便后续获取队列数据个数。
5.🍺队头出数据🍺
//队头出数据
void QueuePop(Queue* pq)
{
assert(pq);
//1.空链表
assert(!QueueEmpty(pq));
//2.一个结点
//3.多个结点
if (pq->Phead->next == NULL)
{
free(pq->Phead);
pq->Phead = pq->Ptail = NULL;
}
else
{
QNode* next = pq->Phead->next;
free(pq->Phead);
pq->Phead = next;
}
pq->size--;
}
出数据即删除数据。出队头数据即删除头结点,释放头结点空间。需要注意的是,一旦队列删空后将不能再进行删除,因此需要加一个判空处理(判空函数后续讲解)。这里同样要注意一个结点和多个结点的情况,当一个结点时,进行删除,结点空间被释放后除了要将Phead置空外,还需将Ptail置空,因为一个结点时,Phead,Ptail都指向头结点,若不将Ptail置空,将会造成野指针的问题。队头出数据后,将size--,保持队列内数据个数的正确性。
6.🍺获取队头数据🍺
//获取对头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->Phead->data;
}
队列为空时,无法获取队头数据,因此需要对队列进行判空。Phead一直指向队头,前面我们已经做过解释,所以只需要返回Phead内的数据data即可。
7.🍺获取队尾数据🍺
//获取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->Ptail->data;
}
队列为空时,同样无法获取队尾数据,因此需要对队列进行判空。Ptail一直指向队尾,前面我们已经做过解释,所以只需要返回Ptail内的数据data即可。
8.🍺判空🍺
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->Phead == NULL
&& pq->Ptail == NULL;
}
前面我们说过,Phead一直指向队头,Ptail一直指向队尾。那么也就是说,当队列内只有一个结点时,Phead等于Ptail且不为空;当队列内存在多个结点时,Phead不等于Ptail;当队列为空时,Phead和Ptail皆为空。所以,当Phead,Ptail都为空时,表达式( pq->Phead == NULL && pq->Ptail == NULL)为真,函数返回true。
9.🍺获取队列数据个数🍺
//队列数据个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
获取队列数据个数直接返回size即可,这就是为什么一开始定义队列结构体时加上size的原因。队列在进行数据的插入或是删除时,size就一直在记录着队列内的数据个数,这就使得获取队列数据个数变得十分容易。
四.🍻队列的整体实现🍻
1.🍺Queue.h🍺
头文件的包含以及类型和函数的声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//定义类型
//链表队列
//队列特点:先进先出,队尾入数据,队头出数据
typedef int QDataType;
typedef struct QNode
{
QDataType data;
struct QNode* next;
}QNode;
typedef struct Queue
{
QNode* Phead;
QNode* Ptail;
int size;
}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);
2.🍺Queue.c🍺
函数及功能的实现
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"
//初始化队列
void QueueInit(Queue* pq)
{
assert(pq);
pq->Phead = pq->Ptail = NULL;
pq->size = 0;
}
//销毁队列
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->Phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->Phead = pq->Ptail = NULL;
pq->size = 0;
}
//队尾入数据
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->data = x;
newnode->next = NULL;
if (pq->Phead == NULL)
{
pq->Phead = pq->Ptail = newnode;
}
else
{
pq->Ptail->next = newnode;
pq->Ptail = newnode;
}
pq->size++;
}
//队头出数据
void QueuePop(Queue* pq)
{
assert(pq);
//1.空链表
assert(!QueueEmpty(pq));
//2.一个结点
//3.多个结点
if (pq->Phead->next == NULL)
{
free(pq->Phead);
pq->Phead = pq->Ptail = NULL;
}
else
{
QNode* next = pq->Phead->next;
free(pq->Phead);
pq->Phead = next;
}
pq->size--;
}
//获取对头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->Phead->data;
}
//获取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->Ptail->data;
}
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->Phead == NULL
&& pq->Ptail == NULL;
}
//队列数据个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
3.🍺test.c🍺
功能及逻辑测试
#define _CRT_SECURE_NO_WARNINGS
#include"Queue.h"
void TestQueue()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
//printf("%d ", QueueFront(&q));
//QueuePop(&q);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePush(&q, 5);
printf("Size:%d\n", QueueSize(&q));
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestroy(&q);
}
int main()
{
TestQueue();
return 0;
}
本篇以队列的实现为主题的博客到此就要结束了,希望本篇博客可以帮助到各位小伙伴,博主码字不易,如果本篇博客对你有所帮助,还望各位小伙伴点赞👍,收藏⭐+关注,感谢各位的支持!如果有什么不明白的地方或是另有其他的高见,也可以在评论区留言,博主也会及时回复或进行更改,欢迎指正,谢谢!