【数据结构】5.2循环队列(C语言)

【数据结构】——5.2 循环队列

一、循环队列的概念及结构

1. 顺序队列的问题

  1. 顺序队列出队列是删除队头元素,队列中的所有元素都得向前移动一位,以保证队列的队头不空缺,此时的时间复杂度为0(n)
  2. 若是不移动队列中的所有元素,使用头指针标记队列的起始位置,每次出队列都将头指针后移,效率就会高很多。
  3. 但是当尾指针移动到队列末尾,头指针不在空间的头部,则会导致队列前面仍有空间,但是插入数据会越界,这就是队列的假溢出

假溢出队列

​ 所以为了解决该问题,在队尾指针到达末尾再次插入数据时,让数据插入到队列的头部位置,使队列构成一个循环,这就是循环队列

2. 循环队列的概念

循环队列:就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用

​ 循环队列物理上是一个线性结构,逻辑上是一个循环的圈

查看源图像

3. 循环队列的结构定义

typedef int CQDataType;
typedef struct CQueue
{
	CQDataType* data;	 //数据数组
	int head;			//头指针(其实是存储的下标)
	int rear;			//尾指针
	int capacity;		//容量大小
} CQueue;

二、循环队列的问题

1. 如何构成循环

​ 队列插入时,若是尾指针指向队列末尾,且队列头部仍有空间,则需要队尾指针指向头部,那如何形成这样一个循环呢

  1. 队列每次插入时,尾指针都会加一,若是尾指针指向队尾,则加一就会造成越界

  2. 若是给加一后尾指针对队列容量进行余运算,则尾指针又会回到队列开头的0下标,所以下标的相加都要余运算容量

    h e a d = ( h e a d + 1 )   %   c a p a c i t y head = (head + 1)\ \% \ capacity head=(head+1) % capacity

循环过来的队列

2. 如何判断队列的空和满

​ 循环队列为空时,头指针和尾指针指向同一个位置,若是循环队列满了,头指针和尾指针依然指向同一个位置,此时无法区分队列是空的还是满的

判空和判满的问题

​ 为了区分队列为空和为满的情况,有2种解决方法

  1. 在队列中声明成员变量,用来记录队列的大小,若是队列大小为0则为空,若是队列大小为队列的容量则为满
  2. 在循环队列中空出一个元素的位置,在队列满时避免了队头和队尾指针相等的情况(我们使用这种方法实现)

判空和判满的解决

三、循环队列的实现

1. 初始化

  1. 先为队列申请空间,再为数据数组申请空间
  2. 初始化头尾指针,都赋值为0
  3. 初始化容量使用#define声明,容量也初始化为队列容量大小(这里的队列是静态的,没有扩容操作)
#define MAX_DATA 100
void CQueueInit(CQueue** ppq)
{
	assert(ppq != NULL);

	*ppq = (CQueue*)malloc(sizeof(CQueue));
	if (*ppq == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}

	(*ppq)->data = (CQDataType*)malloc(MAX_DATA * sizeof(CQDataType));
	if ((*ppq)->data == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}

	(*ppq)->head = (*ppq)->rear = 0;
	(*ppq)->capacity = MAX_DATA;
}

2. 入队列

  1. 先判满,若是满了则不进行操作(这里对满队列不做处理,没有扩容)
  2. 数值赋值,尾指针后移(带循环的后移)
void CQueuePush(CQueue* pq, CQDataType x)
{
	assert(pq != NULL);

	if (CQueueFull(pq))
	{
		perror("Cycle Queue is Full!\n");
		return;
	}

	pq->data[pq->rear] = x;
	pq->rear = (pq->rear + 1) % pq->capacity;
}

3. 出队列

  1. 先判读队列是否为空(这里对空队列不做处理)
  2. 将头指针循环后移即可
void CQueuePop(CQueue* pq)
{
	assert(pq != NULL);

	if (CQueueEmpty(pq))
	{
		return;
	}

	pq->head = (pq->head + 1) % pq->capacity;
}

4. 获取队头元素

  1. 队列为空断言处理
  2. 直接返回头指针下标的数据
CQDataType CQueueFront(CQueue* pq)
{
	assert(pq != NULL);
	assert(!CQueueEmpty(pq));

	return pq->data[pq->head];
}

5. 获取队尾元素

  1. 队列为空断言处理
  2. 直接返回尾指针下标的数据
CQDataType CQueueBack(CQueue* pq)
{
	assert(pq != NULL);
	assert(!CQueueEmpty(pq));

	return pq->data[(pq->rear - 1 + pq->capacity) % pq->capacity];
}

6. 获取元素个数

  1. 这里直接使用尾指针下标减去头指针下标可能得到负数,在减前将尾指针加上容量,就不会出现减后为负数的情况
  2. 减后由于尾指针加过容量,可能会造成数组越界,对减后的值与容量进行余运算就不会出现越界
size_t CQueueSize(CQueue* pq)
{
	assert(pq != NULL);

	return (pq->rear + pq->capacity - pq->head) % pq->capacity;
}

7. 判断队列为空

​ 若是头指针等于尾指针,则队列为空

_Bool CQueueEmpty(CQueue* pq)
{
	assert(pq != NULL);
	return pq->head == pq->rear;
}

8. 判断队列为满

​ 若是尾指针循环后移的位置正好等于头指针,则队列满了

_Bool CQueueFull(CQueue* pq)
{
	assert(pq != NULL);
	return (pq->rear + 1) % pq->capacity == pq->head;
}

9. 销毁

  1. 先释放数据数组,再释放队列
  2. 将队列指针置为NULL
void CQueueDestroy(CQueue** ppq)
{
	assert(ppq != NULL);

	free((*ppq)->data);
	free(*ppq);
	*ppq = NULL;
}

四、完整代码

代码保存在gitee中:点击完整代码参考

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值