1. 背景引入
为了避免当只有一个元素时,队头和队尾重合使得处理变得麻烦,所以引入两个指针front
和rear
。
front
即 队头指针 指向队头元素,rear
即队尾指针 指向 队尾元素的下一个元素。
这会导致牺牲一个空的内存位置,来解决“假溢出”问题(数组越界问题)。
2. 概念
解决假溢出的办法就是:后面满了,再从头开始,也就是头尾相接的循环。
我们把队列的这种 头尾相接的 顺序存储结构 称为循环队列。
2.1. 特点
- 逻辑结构:线性结构
- 物理结构:顺序结构
2.2. 注意的问题
- 循环队列,要特别注意 入队出队后,队头队尾变量的更新方式
-
- 注意理解这两种更新办法的 原因
- 队列长度计算,当做公式理解,利用取余特性
3. 接口实现
3.1. 定义 操作 顺序循环队列的 结构体
队列 或者 栈,都是对 顺序表 进行受限操作来实现的。
所以,结构体顺带开辟 数组作为顺序表空间。
3.2. 创建空的 顺序循环队列(头尾 相等)
不管 队头队尾指针下标为多少,只要相等,就是空的 队列
3.3. 入队(栈尾后移)
注意,队头、队尾,下标更新的方式。
入队,从队尾进入,即队尾指针后移。
但要注意,入队后,队尾指针后移,其下标所在的空间是不允许被使用的。
所以,N个内存宽度的数组,只有N-1可以真的使用,剩下一个浪费掉,作为判断用。
3.4. 出队(队头后移)
注意,队头、队尾,下标更新的方式。
队头指针后移即可
3.5. 队列 长度(即当前被用上的 元素个数)
最长的长度,就是N-1,总有个空间不允许被使用
3.6. 清空(头尾 相等,即不停的出队,直到队空)
队头队尾,指向相同即可。
4. 总体代码
#include <stdio.h>
#include <stdlib.h>
#define DEBUG(Str) printf("%s %s %s %d\n", Str, __func__, __FILE__, __LINE__)
// 定义 操作 顺序循环队列的 结构体
#define N 5 //队列里的顺序表长度
typedef int SQDataType;
typedef struct SeqQueue
{
SQDataType data[N]; //直接开辟定长数组
int front; //队头 下标
int rear; //队尾 下标
} SQ;
// 创建空的 顺序循环队列(头尾 相等)
// 形参为空,一定写成void,避免用时意外传值,段错误难排查
SQ *SQInit(void)
{
// 开辟 操作 顺序循环队列的 结构体
SQ *PQ = (SQ *)malloc(sizeof(SQ));
if (NULL == PQ)
{
DEBUG("SQInit err");
return NULL;
}
// 初始化 队头 队尾 下标,为相同值即可
PQ->front = PQ->rear = 0;
return PQ;
}
// 入队(栈尾后移)
int SQPush(SQ *PQ, SQDataType data)
{
// 判断 满不满,满了不能入队
// 因为从队尾进,若满,队尾 下个位置就是 队头
if ((PQ->rear + 1) % N == PQ->front)
{
printf("now wanna push data:%d\n", data);
DEBUG("SQPush failed, FULL");
return -1;
}
// 数据入列
// 把上回不可用的队尾位置,变为可用;
// 队尾挪到下个位置,继续不可用。
// 循环队列就算满了,必会有一个rear位置,不可被使用
PQ->data[PQ->rear] = data;
// 必须这种方法 赋值更新位置,因为要循环
PQ->rear = (PQ->rear + 1) % N;
return 0;
}
// 出队(队头后移)
SQDataType SQPop(SQ *PQ)
{
// 空则不能出队
if (PQ->rear == PQ->front)
{
DEBUG("SQPop failed, SQ is empty");
return (SQDataType)(-1);
}
// 临时变量接收 要出队的数据
SQDataType data = PQ->data[PQ->front];
// 出队是 队头 后移
PQ->front = (PQ->front + 1) % N;
// 返回数据
return data;
}
// 队列 长度(即当前被用上的 元素个数)
int SQLength(SQ *PQ)
{
// 队尾先加,因为了逻辑上 队尾编号在后
// 加N 为保证 队尾减对头 不出负数
// 就算为正,用%N 也可以去除 商,只保留 余数
return (PQ->rear + N - PQ->front) % N;
}
// 清空(头尾 相等,即不停的出队,直到队空)
void SQClear(SQ *PQ)
{
#if 0 //都行
while (PQ->front != PQ->rear)
SQPop(PQ);
#elif 1
// 同值即可
PQ->front = PQ->rear;
#endif
}
int main(int argc, char *argv[])
{
// 创建一个空的队列
SQ *PQ = SQInit();
// 入列 0 1 2 3 4
// 注意:N为5个内存长度,实际只有N-1 4个可以用
for (int i = 1; i <= N; i++)
SQPush(PQ, i * 11);
// 出队
// 队头出队,队头指针 后移
printf("front:%d\n", SQPop(PQ));
// 有效 队列元素个数:长度
// 22 33 44
printf("SeqQueu Length:%d\n", SQLength(PQ));
// 清空队列
SQClear(PQ);
printf("-----SeqQueu Length:%d\n", SQLength(PQ));
// 释放循环队列
free(PQ);
PQ = NULL;
}