【数据结构】循环队列(数组实现)

目录

一、循环队列定义

怎么使一个数组在逻辑上呈"环状"呢?

二、循环队列与顺序队列的差异

1、存储方式:

2、操作方式:

3、空间利用率:

4、循环队列判断队空的方式:

5、循环队列判断队满的方式

完整测试代码及注释:

总结:


一、循环队列定义

将顺序存储队列的元素的一维数组首尾相接,形成一个环状,如下图所示,这种形式表示的队列称为循环队列。循环队列仍然是顺序队列结构,只是逻辑上和前面的顺序队列有所不同。

复制代码
#define MAXLEN 6           // 定义环形队列的最大长度为 6

typedef int DataType;     // 定义数据类型为整型

typedef struct CircularQueue  // 定义环形队列的结构体
{
    DataType a[MAXLEN];   // 定义存储数据的数组
    int front, rear;      // 定义队头和队尾指针
    int size;             // 定义队列元素个数
} CQueue;

void InitCQueue(CQueue* q)  // 初始化环形队列
{
    q->front = q->rear = 0; // 队头和队尾指针都指向队列的开始位置
    q->size = 0;            // 队列元素个数为 0,即初始为空队列
}

怎么使一个数组在逻辑上呈"环状"呢?

在数据结构中,可以使用一个 front 指针和一个 rear 指针来表示环状队列的队头和队尾位置,当 rear 指针移动到数组的最后一个位置时,如果再有元素需要入队,那么应该将 rear 指针指向数组的第一个位置。 同样地,当 front 指针移动到数组的最后一个位置时,如果还有元素需要出队,那么应该将 front 指针指向数组的第一个位置。

具体实现方法如下:

  1. 初始化:定义一个数组和两个指针 front 和 rear,初始化时,将 front 指针和 rear 指针都指向数组的第一个位置。

  2. 入队:如果队列未满,则将元素插入 rear 指向的位置,然后将 rear 指针后移一位。当 rear 指针移动到数组的最后一个位置时,若队列未满,则将 rear 指针指向数组的第一个位置。

  3. 出队:如果队列非空,则将队头元素取出,然后将 front 指针后移一位。当 front 指针移动到数组的最后一个位置时,只要队列非空,就将 front 指针指向数组的第一个位置。

假设队列开辟的数组单元数为MAXSIZE,它的数组下标在0~MAXSIZE-1之间,若使队头或队尾增1,且使front和rear指针对应的数组下标保持在数组范围内 ,可以利用取模运算实现。


例如,在下图所示的循环队列示意图最大空间为MAXSIZE=8,数组下标为0~7之间。

非空队时如图(2)中队头指针front指向队列中队头元素的前一个位置队尾指针rear 指向队列的队尾元素位置。

  • 入队时的队尾指针加1操作修改为: rear=(rear+1)%MAXSIZE;
  • 出队时的队头指针加1操作修改为:front=(front+1)%MAXSIZE;

入队代码实现:

复制代码
void CQueuePush(CQueue* q, DataType x)   // 元素入队
{
    assert(q);  // 判断 q 是否为空
    if (!CQueueFull(q))  // 如果队列未满
    {
        q->rear = (q->rear + 1) % MAXLEN;   // 队尾指针后移一位
        q->a[q->rear] = x;  // 在队尾处添加元素
        q->size++;  // 队列元素个数加 1
    }
    else    // 队列已满,无法添加数据
    {
        printf("队列已满,无法添加数据!\n");
        exit(-1);
    }
}

出队代码实现:

复制代码
void CQueuePop(CQueue* q)   // 元素出队
{
    assert(q);  // 判断 q 是否为空
    if (!CQueueEmpty(q))    // 如果队列非空
    {
        q->front = (q->front + 1) % MAXLEN; // 队头指针后移一位
        q->size--;  // 队列元素个数减 1
    }
    else    // 队列已空,无法删除数据
    {
        printf("队列已空,无法删除数据!\n");
        exit(-1);
    }
}

二、循环队列与顺序队列的差异

1、存储方式:

  • 顺序队列:使用数组作为底层数据结构,按照顺序存储元素。
  • 循环队列:仍然使用数组作为底层数据结构,但是通过循环利用数组的空间,实现循环存储。

2、操作方式:

  • 顺序队列:使用两个指针front和rear分别表示队头和队尾,元素入队时rear指针后移,元素出队时front指针后移。
  • 循环队列:同样使用两个指针front和rear表示队头和队尾,但是在数据满或空的状态下,指针继续向后移动的时候保持循环关系。
    • 入队时队尾指针+1:rear=(rear+1)%MAXSIZE;
    • 出队时队头指针+1:front=(front+1)%MAXSIZE;

3、空间利用率:

  • 顺序队列:存储元素的空间是连续的,当队列未满但是数组的末尾已经被利用时,无法继续插入元素。
  • 循环队列:通过循环利用数组空间,解决了顺序队列存储空间的浪费问题,可以实现更高的空间利用率。

4、循环队列判断队空的方式:

图(1)中队头与队尾指针指向同一位置时为空队,判断方法与顺序队列一致。

代码实现:

复制代码
int CQueueEmpty(CQueue* q)  // 判断队列是否为空
{
    assert(q);  // 判断 q 是否为空
    if (q->front == q->rear)  // 通过队头和队尾指针是否相等,判断队列是否为空
    {
        return 1;   // 队列为空
    }
    return 0;       // 队列非空
}

5、循环队列判断队满的方式

由 图(3) 可见,循环队列解决了顺序队列中"假溢出"的现象 ,充分利用了固定长度的队列中的空间。我们知道,在长度不可增长的顺序队列中,判断队列是否队满的条件是rear==MAXLEN。那么在循环队列中,我们判断队满 的方式则为:(rear+1)%MAXLEN==front;

代码实现:

复制代码
int CQueueFull(CQueue* q)   // 判断队列是否为满
{
    assert(q);  // 判断 q 是否为空
    if ((q->rear + 1) % MAXLEN == q->front)  // 通过队尾和队头指针是否相邻,判断队列是否为满
    {
        return 1;   // 队列为满
    }
    return 0;       // 队列未满
}

我们理解完顺序队列与循环队列的差异后,在固定长度代码的基础上对front、rear指针的移动判满操作进行修改即可得到循环队列的代码。


完整测试代码及注释:

复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


#define MAXLEN 6           // 定义环形队列的最大长度为 6

typedef int DataType;     // 定义数据类型为整型

typedef struct CircularQueue  // 定义环形队列的结构体
{
    DataType a[MAXLEN];   // 定义存储数据的数组
    int front, rear;      // 定义队头和队尾指针
    int size;             // 定义队列元素个数
} CQueue;

void InitCQueue(CQueue* q)  // 初始化环形队列
{
    q->front = q->rear = 0; // 队头和队尾指针都指向队列的开始位置
    q->size = 0;            // 队列元素个数为 0,即初始为空队列
}

int CQueueFull(CQueue* q)   // 判断队列是否为满
{
    assert(q);  // 判断 q 是否为空
    if ((q->rear + 1) % MAXLEN == q->front)  // 通过队尾和队头指针是否相邻,判断队列是否为满
    {
        return 1;   // 队列为满
    }
    return 0;       // 队列未满
}

int CQueueEmpty(CQueue* q)  // 判断队列是否为空
{
    assert(q);  // 判断 q 是否为空
    if (q->front == q->rear)  // 通过队头和队尾指针是否相等,判断队列是否为空
    {
        return 1;   // 队列为空
    }
    return 0;       // 队列非空
}

void CQueuePush(CQueue* q, DataType x)   // 元素入队
{
    assert(q);  // 判断 q 是否为空
    if (!CQueueFull(q))  // 如果队列未满
    {
        q->rear = (q->rear + 1) % MAXLEN;   // 队尾指针后移一位
        q->a[q->rear] = x;  // 在队尾处添加元素
        q->size++;  // 队列元素个数加 1
    }
    else    // 队列已满,无法添加数据
    {
        printf("队列已满,无法添加数据!\n");
        exit(-1);
    }
}

void CQueuePop(CQueue* q)   // 元素出队
{
    assert(q);  // 判断 q 是否为空
    if (!CQueueEmpty(q))    // 如果队列非空
    {
        q->front = (q->front + 1) % MAXLEN; // 队头指针后移一位
        q->size--;  // 队列元素个数减 1
    }
    else    // 队列已空,无法删除数据
    {
        printf("队列已空,无法删除数据!\n");
        exit(-1);
    }
}

int CQueueTop(CQueue* q)   // 获取队首元素
{
    if (!CQueueEmpty(q))    // 如果队列非空
    {
        return q->a[q->front + 1];  // 返回队头下一个位置的元素
    }
    else    // 队列已空,无法获取队首数据
    {
        printf("队列已空,无法获取队首数据!\n");
        exit(-1);
    }
}

int CQueueTail(CQueue* q)   // 获取队尾元素
{
    if (!CQueueEmpty(q))    // 如果队列非空
    {
        return q->a[q->rear];   // 返回队尾位置的元素
    }
    else    // 队列已空,无法获取队尾数据
    {
        printf("队列已空,无法获取队尾数据!\n");
        exit(-1);
    }
}


int main()
{
	CQueue q;
	InitCQueue(&q);
	CQueuePush(&q, 1);
	CQueuePush(&q, 2);
	CQueuePush(&q, 3);
	CQueuePush(&q, 4);
	CQueuePush(&q, 5);
	CQueuePop(&q);
	CQueuePop(&q);
	CQueuePop(&q);
	CQueuePop(&q);
	//CQueuePop(&q);
	int x;
	x = CQueueTop(&q);
	printf("%d\n", x);
	x = CQueueTail(&q);
	printf("%d\n", x);
    return 0;
}

总结:

循环队列通过环形数组的设计,充分利用了存储空间,并实现了高效的元素入队和出队操作。在使用循环队列时,需要特别注意队列为空和队列满的判断,避免出现错误。

相关推荐
程序喵大人1 小时前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
hanlin031 小时前
动态规划专练:力扣第121、122题
笔记·算法·leetcode
To_OC1 小时前
LC 74 搜索二维矩阵:换皮的二分查找,我居然一开始没看出来
javascript·算法·leetcode
文心快码BaiduComate1 小时前
文心快码荣获“中国优秀软件产品”,实力再获国家级认可
算法·代码规范
玖玥拾1 小时前
LeetCode 189 轮转数组
算法·leetcode
ZhangShao06072 小时前
题解:CF2249B
c++·算法
小玮看世界2 小时前
[Python]线段树与二分法
数据结构·算法
Tisfy4 小时前
LeetCode 3345.最小可整除数位乘积 I:暴力枚举(从n开始尝试)
数学·算法·leetcode·题解·枚举
DolphinDB智臾科技4 小时前
告别 TB 级数据搬运:DolphinDB 滤波算法库,重塑物联网数据价值链
物联网·算法
可编程芯片开发5 小时前
基于双PI控制器结构的六步逆变器供电无刷直流电机调速simulink仿真
算法