【数据结构】——5.2 循环队列
目录
一、循环队列的概念及结构
1. 顺序队列的问题
- 顺序队列出队列是删除队头元素,队列中的所有元素都得向前移动一位,以保证队列的队头不空缺,此时的时间复杂度为0(n)
- 若是不移动队列中的所有元素,使用头指针标记队列的起始位置,每次出队列都将头指针后移,效率就会高很多。
- 但是当尾指针移动到队列末尾,头指针不在空间的头部,则会导致队列前面仍有空间,但是插入数据会越界,这就是队列的假溢出
所以为了解决该问题,在队尾指针到达末尾再次插入数据时,让数据插入到队列的头部位置,使队列构成一个循环,这就是循环队列
2. 循环队列的概念
循环队列:就是将队列存储空间的最后一个位置绕到第一个位置,形成逻辑上的环状空间,供队列循环使用
循环队列物理上是一个线性结构,逻辑上是一个循环的圈
3. 循环队列的结构定义
typedef int CQDataType;
typedef struct CQueue
{
CQDataType* data; //数据数组
int head; //头指针(其实是存储的下标)
int rear; //尾指针
int capacity; //容量大小
} CQueue;
二、循环队列的问题
1. 如何构成循环
队列插入时,若是尾指针指向队列末尾,且队列头部仍有空间,则需要队尾指针指向头部,那如何形成这样一个循环呢
-
队列每次插入时,尾指针都会加一,若是尾指针指向队尾,则加一就会造成越界
-
若是给加一后尾指针对队列容量进行余运算,则尾指针又会回到队列开头的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种解决方法
- 在队列中声明成员变量,用来记录队列的大小,若是队列大小为0则为空,若是队列大小为队列的容量则为满
- 在循环队列中空出一个元素的位置,在队列满时避免了队头和队尾指针相等的情况(我们使用这种方法实现)
三、循环队列的实现
1. 初始化
- 先为队列申请空间,再为数据数组申请空间
- 初始化头尾指针,都赋值为0
- 初始化容量使用
#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. 入队列
- 先判满,若是满了则不进行操作(这里对满队列不做处理,没有扩容)
- 数值赋值,尾指针后移(带循环的后移)
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. 出队列
- 先判读队列是否为空(这里对空队列不做处理)
- 将头指针循环后移即可
void CQueuePop(CQueue* pq)
{
assert(pq != NULL);
if (CQueueEmpty(pq))
{
return;
}
pq->head = (pq->head + 1) % pq->capacity;
}
4. 获取队头元素
- 队列为空断言处理
- 直接返回头指针下标的数据
CQDataType CQueueFront(CQueue* pq)
{
assert(pq != NULL);
assert(!CQueueEmpty(pq));
return pq->data[pq->head];
}
5. 获取队尾元素
- 队列为空断言处理
- 直接返回尾指针下标的数据
CQDataType CQueueBack(CQueue* pq)
{
assert(pq != NULL);
assert(!CQueueEmpty(pq));
return pq->data[(pq->rear - 1 + pq->capacity) % pq->capacity];
}
6. 获取元素个数
- 这里直接使用尾指针下标减去头指针下标可能得到负数,在减前将尾指针加上容量,就不会出现减后为负数的情况
- 减后由于尾指针加过容量,可能会造成数组越界,对减后的值与容量进行余运算就不会出现越界
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. 销毁
- 先释放数据数组,再释放队列
- 将队列指针置为NULL
void CQueueDestroy(CQueue** ppq)
{
assert(ppq != NULL);
free((*ppq)->data);
free(*ppq);
*ppq = NULL;
}
四、完整代码
代码保存在gitee中:点击完整代码参考