队列类似于日常生活中的排队,它也是一种特殊的线性表。队列和栈有相反的逻辑,但是却属于同类结构。
队列的介绍
定义: 队列是一种特殊的线性表,它只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 的原则。
入队列:从队列的队尾插入数据的操作
出队列:从队列的队头删除数据的操作
队列的遵循的原则如下图所示
与排队做核酸一样,先排队的人先做,做完离开,后来的人从站在队尾,直到做完核酸离开。
队列的结构
队列是在两端进行操作的,如果采用顺序表的结构的话,出队列时需要将队列中其它元素都向前移动一位,比较麻烦,因此一般情况下队列用链表来实现比较好。下图是队列的抽象结构
队列的结构体的定义
typedef int QDataType;
// 链式结构的队列
// 队列中的结点
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front;
QNode* rear;
}Queue;
队列由一个个结点构成,所以需要定义队列中结点的结构体,队列的结构体中定义头结点和尾结点,因为队列只会队头结点和尾结点操作。
队列的实现
(1)队列初始化
void QueueInit(Queue* q)
{
assert(q);
q->front = NULL;
q->rear = NULL;
}
队列是需要初始化的,初始时,头结点和尾结点指针都指向NULL。
(2)判断队列是否为空。为空返回1,非空返回0。
int QueueEmpty(Queue* q)
{
assert(q);
return q->front == NULL;
}
因为后面的多个操作需要判断队列是否为空,所以先实现这个函数。
(3)入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
// 创建结点
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
// 为结点赋值
newnode->data = data;
newnode->next = NULL;
// 判断队列是否为空
if (QueueEmpty(q))
{
// 队列为空,插入队列的结点既是头结点也是尾结点
q->front = q->rear = newnode;
}
else
{
// 队列非空,让尾结点指向要插入的结点,然后插入的结点为新的尾结点
q->rear->next = newnode;
q->rear = newnode;
}
}
入队列的步骤:
- 先创建一个没有指向的结点,给结点赋值;
- 判断队列是否为空
空:插入队列的结点既是头结点也是尾结点
非空:让尾结点指向要插入的结点,然后插入的结点为新的尾结点
(4)出队列
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
// 如果只剩一个结点的情况
if (q->front->next == NULL)
{
free(q->front);
q->front = q->rear = NULL;
}
else
{
QNode* next = q->front->next;
free(q->front);
q->front = next;
}
}
出队列时需要判断队列中是否只有一个结点。队列中只有一个结点时,出队列后要让队头指针和队尾指针都指向NULL。
(5)队头数据
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->front->data;
}
(6)队尾数据
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->rear->data;
}
(7)队列中元素个数
int QueueSize(Queue* q)
{
assert(q);
int count = 0;
QNode* cur = q->front;
while (cur)
{
count++;
cur = cur->next;
}
return count;
}
(8)销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->front;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
}
完整代码及测试程序
一共三个文件
Queue.h
Queue.c
Test_Queue.c
#pragma once
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
#include<assert.h>
typedef int QDataType;
// 链式结构:表示队列
// 队列中的结点
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* front;
QNode* rear;
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
#include"Queue.h"
void QueueInit(Queue* q)
{
assert(q);
q->front = NULL;
q->rear = NULL;
}
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
printf("malloc fail\n");
exit(-1);
}
newnode->data = data;
newnode->next = NULL;
if (QueueEmpty(q))
{
q->front = q->rear = newnode;
}
else
{
q->rear->next = newnode;
q->rear = newnode;
}
}
void QueuePop(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
// 如果只剩一个结点的情况
if (q->front->next == NULL)
{
free(q->front);
q->front = q->rear = NULL;
}
else
{
QNode* next = q->front->next;
free(q->front);
q->front = next;
}
}
QDataType QueueFront(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->front->data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(!QueueEmpty(q));
return q->rear->data;
}
int QueueSize(Queue* q)
{
assert(q);
int count = 0;
QNode* cur = q->front;
while (cur)
{
count++;
cur = cur->next;
}
return count;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
return q->front == NULL;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* cur = q->front;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
q->front = q->rear = NULL;
}
#include"Queue.h"
Test()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
printf("队头:%d ", QueueFront(&q));
printf("队尾:%d\n", QueueBack(&q));
printf("出队列:\n");
QueuePop(&q);
printf("队头:%d", QueueFront(&q));
printf("队尾:%d\n", QueueBack(&q));
printf("队列大小:%d\n", QueueSize(&q));
printf("全部出队列:");
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestroy(&q);
}
int main()
{
Test();
return 0;
}
循环队列
循环队列的介绍
循环队列可以使用数组来实现的,它里面能够存储的最大元素个数是固定的,它可以在数组前面的元素出队列后,把前面的位置当成尾来插入数据,因此可以实现对数组的这内存的循环使用,也就是说可以重复利用之前用过的空间。但是操作逻辑还是跟队列一致,先进先出、后进后出,因此称为循环队列。
要实现循环队列,那肯定在队尾下标超出数组下标后,要让队尾下标回到0位置,再插入,但是这个时候会遇到队空队满无法判断的问题,如下图所示,在队满和队空时会遇到同样的情况tail==front
为了解决上述问题,可以采取空一个位置的方式设计队列。接下来结合下面这个图来说明循环队列的实现方式。
在上图中A、B、C指的是队列的三种状态,顺序是从A到C,这三种状态演示了如何循环使用数组。在队列中,front是队头元素的下标,tail是队尾元素的下标+1,绿色填充方块是插入的数据,灰色是空下来的空间。假设队列最大可以存储k个元素(上图中为9),但是我们需要开辟k+1个单位的空间(下标从0到9,k+1=9+1=10),将tail位置的空间空出来。当下标超过数组下标是时,回到数组开始的下标(也就是让下标等于0),即tail和font的值在区间[0,9]上循环,很多书上是用取余,本质都是让超过数组下标后回到开始的下标。此时队空的判断条件为tail==front
,队满的判断条件为tail+1==front
。
A状态:front=0,tail=9,tail+1==9+1==10
,超过数组下标了,所以把tail+1看作0(这块具体可以参考判断队列满的代码,代码中实际上是定义了一个变量,用这个变量来跟front比较),此时正好等于front,所以是队满状态;在B状态:先出2个数据,此时front=2,那么根据前面的条件判断队非满,所以在9位置处插入数据,然后tail++,此时tail==10== 9+1 == k+1
,所以让tail=0;C状态:插入一个数据后,tail=1,此时tail+1==(1+1)==2==front
,表示队列已满。
循环队列的实现
结构体的定义
typedef struct {
int* a; // 数组
int k; // 队列最多能存多少个元素
int front; // 队头下标
int tail; // 队尾(队尾元素的下一个下标)
} MyCircularQueue;
注意: k指的是队列中可以放进去的元素个数,不是开辟的数组大小。k为9时,开辟的空间应该k+1个。
(1)队列初始化
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (int*)malloc(sizeof(int)* (k+1));
obj->k = k;
obj->front = 0;
obj->tail = 0;
return obj;
}
(2)判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->tail;
}
(3)判断队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
// 如果满的话tail+1 == front
int tailNext = obj->tail + 1;
if (tailNext == obj->k+1) // 类似于对k+1取余
{
tailNext = 0;
}
return tailNext == obj->front;
}
定义变量tailNext的原因: 避免tai在数组最后一个位置时,tail+1超过数组下标,应该回到数组下标0位置,但是又不能让tail+1等于0,所以用这个tailNext这个临时变量来帮助判断。
(4)往队列中插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->a[obj->tail] = value;
obj->tail++;
if (obj->tail == obj->k+1) // 超出数组下标后回到0位置
{
obj->tail = 0;
}
return true;
}
}
(5)从队列中出数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
obj->front++;
if (obj->front == obj->k+1)
{
obj->front = 0;
}
return true;
}
}
(6)队头数据
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->front];
}
(7)队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
return -1;
else
{
int tailPrev = obj->tail-1;
if (tailPrev == -1) // 如果tail指向队列第一个位置,则队尾在第k个位置
{
tailPrev = obj->k;
}
return obj->a[tailPrev];
}
}
(8)销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
完整代码
typedef struct {
int* a; // 数组
int k; // 队列最多能存多少个数据
int front; // 队头
int tail; // 队尾(队尾数据的下一个下标)
} MyCircularQueue;
// 创建循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (int*)malloc(sizeof(int)* (k+1));
obj->k = k;
obj->front = 0;
obj->tail = 0;
return obj;
}
// 判断队列是否为空
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front == obj->tail;
}
// 判断队列是否已满
bool myCircularQueueIsFull(MyCircularQueue* obj) {
// 如果满的话tail+1 == front
int tailNext = obj->tail + 1;
if (tailNext == obj->k+1)
{
tailNext = 0;
}
return tailNext == obj->front;
}
// 往队列中插入数据
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->a[obj->tail] = value;
obj->tail++;
if (obj->tail == obj->k+1)
{
obj->tail = 0;
}
return true;
}
}
// 从队列中出数据
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
obj->front++;
if (obj->front == obj->k+1)
{
obj->front = 0;
}
return true;
}
}
// 队头数据
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
return -1;
else
return obj->a[obj->front];
}
// 队尾数据
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
return -1;
else
{
int tailPrev = obj->tail-1;
if (tailPrev == -1) // 如果tail指向队列第一个位置,则队尾在第k个位置
{
tailPrev = obj->k;
}
return obj->a[tailPrev];
}
}
// 销毁队列
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}