队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为对头
队列可以通过数组或者链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列是在数组头上出数据,效率会比较低。
使用链表来定义队列,这个链表由两个结构体定义,其中一个结构体中的成员包括存储的数据和下一个结构体的地址,另一个结构体中的成员包含对头和队尾的地址。如下:
//队列用单链表实现 typedef int QDataType; typedef struct QueueNode { struct QueueNode* next; QDataType data; }QNode; typedef struct Queue { QNode* head; QNode* tail; }Queue;
//队列初始化 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; //对于链表来说,此时新插入进来的数据变成尾部 } }
- 使用链表存储数据,不会造成空间多余,所以当要插入数据时要先进行动态内存开辟空间,申请一个QNode结构体大小的空间用来存放要插入的数据。
- 如果尾节点指向的是空节点(也可以说头节点指向的是空节点),说明对列中还没有存储数据,此时新插入进来的数据为第一个数据,对头与队尾都指向这个新插入进来的数据。
- 如果队列中已经有多个数据了,将新插入进来的节点与尾节点相连接,然后将这个新插入的节点作为新的尾节点。
//判空函数 bool QueueEmpty(Queue* pq) { assert(pq); return pq->head == NULL && pq->tail == NULL; }
- 对头与队尾都指向空时,说明队列中没有存储数据,队列为空。
- 队列为空时返回值为真,队列不为空时返回值为假
//出数据(因为队列是先进先出,所以出的是头部的数据) 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; } }
- 队列出数据要先保证队列中已经存储了数据,所以队列在出数据之前要借助判空函数断言一下。
- 如果对头的下一个节点为空,说明队列中只剩一个数据,此时直接将这个节点free掉,然后将头节点与尾节点指向的地址置为空。
- 队列先进先出,队列出数据从对头开始出,直接将对头指向的下一个节点作为新的对头,并且将之前的对头指向的结构体free掉并且置空。
//取头部的数据 QDataType QueueFront(Queue* pq) { assert(pq); assert(!QueueEmpty(pq)); //保证队列不能为空 return pq->head->data; }
- 取数据之前先判空
- 直接将头节点中的数据返回。
在主函数中调用
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; }
- 通过数据插入将1 2 3 4依次插入到队列中,然后通过取头部数据与出数据将队列中的数据打印了出来。
//取尾部的数据 QDataType QueueBack(Queue* pq) { assert(pq); assert(!QueueEmpty(pq)); //保证队列不能为空 return pq->tail->data; }
- 取数据之前先判空
- 直接将尾部节点中的数据返回。
//计算队列的大小 int QueueSize(Queue* pq) { assert(pq); QNode* cur = pq->head; int n = 0; while (cur) { ++n; cur = cur->next; } return n; }
- 从头节点开始,每遍历一个节点,计数一次,直到尾节点。
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; }