循环队列的搭建
队列的定义
- 一种可以实现"先进先出"的存储结构
- 类似排队过程
队列的分类
- 链式队列
- 静态队列
理解循环队列需要回答7个问题
- 静态队列为什么必须是循环队列?
- 循环队列需要几个参数来确定?
- 循环队列各个参数的含义?
- 循环队列 入队 算法
- 循环队列 出队 算法
- 如何判断循环队列是否为空?
- 如何判断循环队列是否已满?
循环队列的实现
循环队列个人理解
- 上图很好的展示了循环队列的基本结构,可以看到循环队列空间是有限的,就像排队的时候也不能把队伍无限排吧?
- 由于需要"先进先出",所以需要一个地方进,一个地方出,所以需要两个参数来表示循环队列
- 而循环的含义是:所有的入队、出队都在同一个数组中实现,设计一个轮回,让出入的指针都围着数组转圈,需要模运算来实现
实现过程
1. 定义循环队列结构
\\ 需要两个参数front(前), rear(后)来表示循环队列,而 *pBase 用来创建所需的数组空间,采用pBase[front]/pBase[rear] 的方式来访问元素,
其中front指向“最前面的元素”,rear指向“最尾部元素的后面一个值(方便操作)”,
typedef struct Queue{
int *pBase;
int front;
int rear;
}QUEUE, * PQUEUE;
\\ len 保存数组长度
const int len = 6;
int main(void)
{
PQUEUE q;
return 0;
}
2. 初始化循环队列
void init(PQUEUE pQ);
int main(void)
{
PQUEUE q;
init(q);
return 0;
}
void init(PQUEUE pQ)
{
\\申请数组空间
pQ->pBase = (int *)malloc(sizeof(int) * len);
\\初始时,两个参数都指向数组第一个值即0
pQ->front = 0;
pQ->rear = 0;
return;
}
3. 入队算法
- 由于循环队列是静态队列,无法增长空间,在入队之前需要判断数组是否已满,满了就无法入队啦,那么该如何判断循环队列满了呢?
通常会采用牺牲一个数组元素的方式来判断,利用模运算,如下图,f指向第一个元素,r指向最后一个元素的后一个元素,当r走到第5个元素的时候时,循环队列里有 {0-4} 5个元素,牺牲掉r所在的那个元素,如果不牺牲这个元素的话,让 r 无休止往后走的话,那一切都乱了,当 r 走到 f “前面”,根本无法判断哪些元素是循环队列里面的,所以关键就是令 r 无法"超过" f - 实现方法就是当 (r+1)% 6 == f 时,判断循环队列已满
bool full(PQUEUE pQ);
bool en_queue(PQUEUE pQ, int val);
int main(void)
{
PQUEUE q;
init(q);
en_queue(q,1);
return 0;
}
bool full(PQUEUE pQ)
{
\\如上所述的判断满算法
if ( (pQ->rear+1)%len == pQ->front )
{
return true;
}
else
{
return false;
}
}
bool en_queue(PQUEUE pQ, int val)
{
if ( full(pQ) )
{
return false;
}
else
{
\\将入队元素放入 rear 的位置
pQ->pBase[pQ->rear] = val;
\\将 rear “向前移” ,模运算实现
pQ->rear = (pQ->rear+1)%len;
return true;
}
}
4. 出队算法
- 出队算法较简单,先判断循环队列是否为空,不为空的话之间将 front “向前移” 即可,利用模运算实现
bool empty(PQUEUE pQ);
bool out_queue(PQUEUE pQ, int * pVal);
int main(void)
{
PQUEUE q;
int val;
init(q);
en_queue(q,1);
out_queue(q,&val);
return 0;
}
bool empty(PQUEUE pQ)
{
if ( pQ->front == pQ->rear )
{
return true;
}
else
{
return false;
}
}
bool out_queue(PQUEUE pQ, int * pVal)
{
if ( empty(pQ) )
{
return false;
}
else
{
\\将 front 处保存的值传给 pVal 将其保存
*pVal = pQ->pBase[pQ->front];
\\将 front "向前移"
pQ->front = (pQ->front+1)%len;
return true;
}
}
5. 遍历算法、清队算法
- 实现起来比较简单,直接贴代码
void traverse(PQUEUE pQ);
void clear(PQUEUE pQ);
int main(void)
{
PQUEUE q;
int val;
init(q);
en_queue(q,1);
en_queue(q,2);
en_queue(q,3);
en_queue(q,4);
en_queue(q,5);
traverse(q);
if ( out_queue(q,&val) )
{
printf("出队成功!出队元素是 %d \n", val);
}
else
{
printf("队列为空!出队失败!\n");
}
traverse(q);
clear(q);
if ( out_queue(q,&val) )
{
printf("出队成功!出队元素是 %d \n", val);
}
else
{
printf("队列为空!出队失败!\n");
}
return 0;
}
void traverse(PQUEUE pQ)
{
int p = pQ->front;
while ( p!=pQ->rear )
{
printf("%d ", pQ->pBase[p]);
p = (p+1)%len;
}
printf("\n");
}
void clear(PQUEUE pQ)
{
pQ->front = 0;
pQ->rear = 0;
return;
}
总结
循环队列最关键需要理解为何需要循环,算法都很简单,但是需要注意判断满的算法为什么要这样写?