详解循环队列

定义和特点

定义

循环队列(Circular Queue)是一种先进先出(FIFO)的数据结构,它允许在固定大小的数组中插入和删除元素。与普通队列不同的是,循环队列的尾部与头部相连,当尾部到达数组的最后一个位置时,它会回到数组的第一个位置。

循环队列通常由一个数组和两个指针(front 和 rear)组成。front 指针指向队列的第一个元素,而 rear 指针指向队列的最后一个元素的下一个位置。当队列为空时,两个指针都指向队列的开头。

特点

循环队列的特点包括:

  1. 循环队列的队头指针和队尾指针可以指示队列元素所在的位置,避免删除元素时移动大量元素。
  2. 循环队列只能队尾插入元素、在队头删除元素。
  3. 循环队列是先进先出(First In First Out)的线性表,先进入的元素出队,后进入的元素才能出队。
  4. 循环队列相比普通的队列,元素出队时无需移动大量元素,只需移动头指针。
  5. 循环队列适合处理用户排队等待的情况。
  6. 循环队列需要预先分配大量存储空间。

基本运算

  1. 入队(enqueue):在 rear 指针的下一个位置插入一个元素,并将 rear 指针向前移动一位。如果 rear 指针已经到达数组的末尾,则将其循环移动到数组的开头。
  2. 出队(dequeue):移除 front 指针所指向的元素,并将 front 指针向前移动一位。如果 front 指针已经到达数组的末尾,则将其循环移动到数组的开头。
  3. 判断队列是否为空:当 front 指针和 rear 指针都指向队列的开头时,队列为空。
  4. 判断队列是否已满:当 rear 指针指向队列的最后一个元素的下一个位置时,队列已满。
  5. 查看队首元素:返回 front 指针所指向的元素。
  6. 查看队列长度:返回 rear 指针与 front 指针之间的元素个数。

循环队列的实现

结构体

c 复制代码
#define MAXSIZE 100

typedef int DataType;

typedef struct
{
	DataType data[MAXSIZE];
	int front;
	int rear;
}CirclesQueue;

初始化

这个函数接收一个指向循环队列的指针CirclesQueue *Q,然后设置队列的frontrear指针都指向队列的开头,也就是位置0。这样,队列就处于初始状态,没有任何元素。函数返回0,表示初始化成功

c 复制代码
int init(CirclesQueue *Q)
{
	Q->front = Q->rear = 0;
	return 0;
}

入队

函数首先检查队列是否已满。如果队列已满,则打印一条消息"队列已满!100001",并返回一个错误代码100001。 如果队列没有满,那么函数会计算rear指针的新位置。在循环队列中,当rear指针到达数组的末尾时,它会循环回到数组的开头。这是通过将Q->rear加1并对数组的最大大小MAXSIZE取模来实现的。 最后,新元素x被插入到Q->data[Q->rear]的位置,然后函数返回0,表示操作成功。

c 复制代码
int enqueue(CirclesQueue *Q, DataType x)
{
	if(isfull(Q))
	{
		printf("队列已满!100001\n");
		return 100001;
	}

	Q->rear = (Q->rear+1) % MAXSIZE;
	Q->data[Q->rear] = x;
	return 0;
}

出队

函数首先检查队列是否为空。如果队列为空,则打印一条消息"队列为空!100002",并返回一个错误代码100002。

如果队列不为空,那么函数会计算front指针的新位置。在循环队列中,当front指针到达数组的末尾时,它会循环回到数组的开头。这是通过将Q->front加1并对数组的最大大小MAXSIZE取模来实现的。

然后,函数会将被删除的元素赋值给*x,这是通过从Q->data[Q->front]获取元素并存储在*x指向的位置来实现的。

c 复制代码
int dequeue(CirclesQueue *Q, DataType *x)
{
	if(isempty(Q))
	{
		printf("队列为空!100002\n");
		return 100002;
	}
	Q->front = (Q->front+1) % MAXSIZE;
	*x = Q->data[Q->front];
	return 0;
}

判满和判空

这两个函数都是用于判断循环队列状态的函数。

isempty(CirclesQueue *Q)函数检查队列是否为空。如果队列的front指针和rear指针相等,那么队列为空,函数返回1;否则,队列不为空,函数返回0。

isfull(CirclesQueue *Q)函数检查队列是否已满。如果队列的rear指针加1后对MAXSIZE取模的结果等于front指针,那么队列已满,函数返回1;否则,队列未满,函数返回0。

这两个函数都利用了循环队列的特性:在普通队列中,当front指针到达数组的末尾时,它无法继续前进;而在循环队列中,当front指针到达数组的末尾时,它可以循环回到数组的开头。同样的,对于rear指针也是这样的。因此,当front指针和rear指针相等时,说明队列中没有元素;当rear指针加1后对MAXSIZE取模的结果等于front指针时,说明队列已满。

c 复制代码
int isempty(CirclesQueue *Q)
{
	return (Q->front == Q->rear) ? 1 : 0;
}
int isfull(CirclesQueue *Q)
{
	return (Q->rear+1)%MAXSIZE == Q->front ? 1 : 0;
}

输出队列内容

c 复制代码
// 输出队列内容  
void printQueue(CirclesQueue *Q) {
    int i;
    if (isempty(Q)) {  
        printf("Queue is empty.\n");  
        return;  
    }  
    i = (Q -> front) %MAXSIZE;
    do{
        printf(" %d",Q -> data[(i + 1 % MAXSIZE)]);
        i = (i+1) %MAXSIZE;
    }while(i != Q -> rear);
}

这段代码用于打印循环队列的内容。下面是这段代码的流程:

  1. 首先,函数检查队列是否为空。如果为空,则打印 "Queue is empty." 并返回。
  2. 如果队列不为空,则初始化一个变量 i,该变量从队列的 front 指针的位置开始。
  3. 然后,使用 do-while 循环遍历队列。在每次迭代中,打印当前 i 指向的元素(通过计算 (i + 1 % MAXSIZE)),并将 i 向前移动一位(i = (i+1) %MAXSIZE;)。
  4. 循环继续进行,直到 i 到达队列的 rear 指针的位置。

注意:这个函数假设队列中的元素是整数,并且使用 %MAXSIZE 来确保在数组的边界之外不会产生溢出。

获取队列长度

在循环队列中,队列的长度可以通过计算rear指针和front指针之间的距离来得到。如果rear指针在front指针的前面,那么队列的长度就是rear - front + MAXSIZE;否则,队列的长度就是rear - front。这里使用% MAXSIZE是为了确保在计算队列长度时不会超出数组的边界。

c 复制代码
// 获取队列长度  
int getLength(CirclesQueue *Q) {  
    return (Q->rear - Q->front + MAXSIZE) % MAXSIZE; // 循环队列:若rear在前方,则长度为rear-front+MAXSIZE,否则为rear-front  
}

获取队首元素

通过计算front指针加1后对MAXSIZE取模得到队首元素在数组中的位置,并返回该位置的元素值。这个函数适用于任何情况,无论front指针在哪里,都可以正确获取队列的队首元素。

c 复制代码
// 获取队首元素  
DataType getFront(CirclesQueue* Q) {  
    int i;
    i = (Q -> front) %MAXSIZE;
    return Q -> data[(i + 1 % MAXSIZE)];
}

运行截图

完整代码

  1. CirclesQueue.h
c 复制代码
/*
	CirclesQueue.h
	循环队列
*/

#define MAXSIZE 100

typedef int DataType;

typedef struct
{
	DataType data[MAXSIZE];
	int front;
	int rear;
}CirclesQueue;

/*循环队列初始化*/
int init(CirclesQueue *Q);

/*入队*/
int enqueue(CirclesQueue *Q, DataType x);

/*队满?*/
int isfull(CirclesQueue *Q);

/*出队*/
int dequeue(CirclesQueue *Q, DataType *);

/*队空*/
int isempty(CirclesQueue *Q);

// 输出队列内容  
void printQueue(CirclesQueue *Q);

// 获取队列长度  
int getLength(CirclesQueue *Q);

// 获取队首元素
DataType getFront(CirclesQueue* Q);
  1. CirclesQueue.c
c 复制代码
/*
	CirclesQueue.c
*/
#include "CirclesQueue.h"

/*循环队列初始化*/
int init(CirclesQueue *Q)
{
	Q->front = Q->rear = 0;
	return 0;
}


/*入队*/
int enqueue(CirclesQueue *Q, DataType x)
{
	if(isfull(Q))
	{
		printf("队列已满!100001\n");
		return 100001;
	}

	Q->rear = (Q->rear+1) % MAXSIZE;
	Q->data[Q->rear] = x;
	return 0;
}

/*队满?*/
int isfull(CirclesQueue *Q)
{
	return (Q->rear+1)%MAXSIZE == Q->front ? 1 : 0;
}


/*出队*/
int dequeue(CirclesQueue *Q, DataType *x)
{
	if(isempty(Q))
	{
		printf("队列为空!100002\n");
		return 100002;
	}
	Q->front = (Q->front+1) % MAXSIZE;
	*x = Q->data[Q->front];
	return 0;
}

/*队空*/
int isempty(CirclesQueue *Q)
{
	return (Q->front == Q->rear) ? 1 : 0;
}

// 输出队列内容  
void printQueue(CirclesQueue *Q) {
    int i;
    if (isempty(Q)) {  
        printf("Queue is empty.\n");  
        return;  
    }  
    i = (Q -> front) %MAXSIZE;
    do{
        printf(" %d",Q -> data[(i + 1 % MAXSIZE)]);
        i = (i+1) %MAXSIZE;
    }while(i != Q -> rear);
}

// 获取队列长度  
int getLength(CirclesQueue *Q) {  
    return (Q->rear - Q->front + MAXSIZE) % MAXSIZE; // 循环队列:若rear在前方,则长度为rear-front+MAXSIZE,否则为rear-front  
} 

// 获取队首元素  
DataType getFront(CirclesQueue* Q) {  
    int i;
    i = (Q -> front) %MAXSIZE;
    return Q -> data[(i + 1 % MAXSIZE)];
}
  1. mian.c
c 复制代码
#include <stdio.h>
#include "CirclesQueue.h"
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	CirclesQueue Q;
	DataType x;
	int cmd;
	char yn;
int result;
    char welcome[] = "(?ω?)没钱";
	int i = 0; 
	int m = 0; 
	int n = 0;
    for(i=0;i<strlen(welcome);i++)
	{
		printf("%c",welcome[i]);
		for(m=0;m<10000;m++)
			for(n=0;n<1000;n++)
			{
				;
			}
	}
	printf("\n\n\n");

	do
	{	
		printf("-----------循环队列演示-----------\n");
		printf(" 1. 初始化\n");
		printf(" 2. 入队\n");
		printf(" 3. 出队\n");
		printf(" 4. 队空?\n");
		printf(" 5. 队满\n");
		printf(" 6. 队列元素个数\n");
		printf(" 7. 取队首元素\n");
		printf(" 8. 输出队列\n");
		printf(" 9. 帮助\n");
		printf(" 0. 退出\n");
		printf(" 请选择(0~6):");
		scanf("%d",&cmd);
		switch(cmd)
		{
		case 1:
			init(&Q);
			printf("队列已初始化!\n");
			break;
		case 2:
			printf("请输入要入队的元素x=");
			scanf("%d", &x);
			if(!enqueue(&Q,x))
			{
				printf("元素x=%d已入队\n", x);
			}
			break;
		case 3:
			printf("确定要出队(出队会将删除对首元素, y or n, n)?");
			getchar();
			scanf("%c", &yn);

			if(yn == 'y' || yn == 'Y')
			{
				if(!dequeue(&Q,&x))
				{
					printf("队首元素【%d】已出队!\n", x);
				}
			}
			break;
	    case 4:
			if(isempty(&Q)) printf("队列为空!\n");
			else printf("队列不为空!\n");
			break;
		case 5:
			if(isfull(&Q)) printf("队列已满!\n");
			else printf("队列还没有满!\n");
			break;
		case 6:
			printf("队列的长度:%d\n",getLength(&Q));
			break;
		case 7:
		    printf("队列首元素: %d\n", getFront(&Q));  
            break;
		case 8:
			printf("输出队列:");
			printQueue(&Q);
			printf("\n");
			break;
		case 9:
		    printf("本程序为循环队列的演示程序,由许娜设计开发,\n本人作业如果问题,尽请指教学习!\n");
		    break;
		}

	}while(cmd!=0);


	return 0;
}

总结

循环队列是一种先进先出(FIFO)的数据结构,它可以在固定大小的数组中实现队列的操作。相比于普通队列,循环队列的优点在于其队尾指针可以循环回到数组的开头,使得队列的操作更加高效。

循环队列的应用场景非常广泛,例如在操作系统中实现任务调度、在通信协议中实现数据包的存储和处理、在线程池中管理线程的执行等。循环队列的优点包括高效的插入和删除操作、空间利用率高、可以动态地调整队列大小等。

循环队列的特点包括:

  1. 队头和队尾指针可以循环移动,使得插入和删除操作更加高效。
  2. 队列的大小是固定的,但可以通过一些技巧扩展其容量。
  3. 队列中的元素是先进先出的,即最早插入的元素最早被删除。
  4. 循环队列需要一个额外的空间来区分空队列和满队列的情况。

在循环队列中,队头指针指向队首元素的下一位,队尾指针指向队尾元素的下一位。当插入元素时,队尾指针会向前移动一位;当删除元素时,队头指针会向前移动一位。当队头指针和队尾指针相遇时,说明队列为空;当队尾指针无法向前移动时,说明队列已满。

循环队列的优点在于其高效的插入和删除操作,但需要注意的是在实现时需要处理满队列和空队列的情况。此外,循环队列也需要一些技巧来扩展其容量,例如使用多级循环队列等。

总之,循环队列是一种非常实用的数据结构,它可以用于各种应用场景中,并且具有高效的性能和空间利用率高的优点。

参考文献

  1. 李刚 刘万辉. "线性表的结构分析和应用." 9787040461473: 16. 2017.1.1
  2. 文心一言
相关推荐
Themberfue几秒前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
陈序缘25 分钟前
LeetCode讲解篇之322. 零钱兑换
算法·leetcode·职场和发展
-$_$-28 分钟前
【LeetCode HOT 100】详细题解之二叉树篇
数据结构·算法·leetcode
大白飞飞30 分钟前
力扣203.移除链表元素
算法·leetcode·链表
学无止境\n1 小时前
[C语言]指针和数组
c语言·数据结构·算法
黄俊懿1 小时前
【深入理解SpringCloud微服务】手写实现各种限流算法——固定时间窗、滑动时间窗、令牌桶算法、漏桶算法
java·后端·算法·spring cloud·微服务·架构
新缸中之脑1 小时前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
夜雨翦春韭1 小时前
【代码随想录Day29】贪心算法Part03
java·数据结构·算法·leetcode·贪心算法
Curry_Math2 小时前
Acwing 区间DP & 计数类DP
算法
Tisfy2 小时前
LeetCode 1928.规定时间内到达终点的最小花费:动态规划
算法·leetcode·动态规划·