- 创建循环队列并初始化。
- 入队。
- 出队。
- 取对头元素。
- 队列逆序输出:先将队列出队后入栈出栈即可,在此不演示。
为什么我们要使用循环队列?
- 普通顺序队列:
- 在使用普通顺序队列的时,判断队空的操作是rear==front,判断队满的操作是判断rear是否在队列尾部。我们不难发现当图(b)与图 (d) 所示情况出现时,rear都处于队列尾部,即程序都将判断队列已满,但是图(d)显然有空,这个就是“真溢出”和“假溢出”的问题。图(b)是真的满了,属于“真溢出”。图(d)显然没满,但是判断已满,就是“假溢出”。
- 为了解决“假溢出”的问题,保证存储空间不浪费,故有了循环队列。
(注:循环队列中的指针rear和front并非真正的指针类型,而是int整型,它们用于记录数组的下标,我们形象化的称之为“指针”)
循环队列如何实现?
-
要实现循环队列,最重要的是如何将指向队列尾元素的指针rear转去指向队列首元素,我们可以采用模”运算:rear=(rear+1)% Maxsize;当队列中的元素在“第一次循环前”,rear+1 < Maxsize(数组下标加一仍小于数组容量),故(rear+1)% Maxsize的值仍等于rear不变(对一个比自己大的数取余,余数仍等于自己本身);当队列中的元素开始“第一次循环时”,rear+1=Maxsize时,(rear+1)% Maxsize==0 即对应了数组下标为0,即开始第一次循环;rear+1 > Maxsize的情况不可能出现,因为在rear+1%Maxsize== 0时就已经开始下一次循环。
-
因此“模”运算非常巧妙的将数组中循环的问题类比化成“取余”这一运算,从而解决问题。
-
循环队列可以高效的利用已出队元素的空间,但是新的问题又出现了,当队满时如图(d1)所示,此时rear==front表示队满;而空队列如图(a)所示,rear==front表示队列空,即 无法判断队满还是队空 。
-
此时我们可以采用两种办法解决:
- 牺牲一个存储单元,如图(d2)所示,人为规定图(d2)的状态为队满,此时队空的条件为:rear==front;队满的条件为:(rear+1)% Maxsize==front;
- 可以设一个计数器,计当前队列中元素的个数,通过比较计数器的数值与数组的最大容量来判断队列状态。
#include <stdio.h>
#include <stdlib.h>
#define Maxsize 10
typedef int dataType;
typedef struct
{
dataType *base;
int front;
int rear;
}CyQueue;
int create(CyQueue *q)
{
q->base=(dataType *)malloc(Maxsize*sizeof(dataType));
if(!q->base)
{
printf("Space allocation failed!\n");
return;
}
q->front=q->rear=0;
return;
}
int EnQueue(CyQueue *q,dataType value)
{
if((q->rear+1)%Maxsize==q->front)
{
printf("Cyclic Queue is Full!\n");
return;
}
q->base[q->rear]=value;
q->rear=(q->rear+1)%Maxsize;
printf("EnQueue Element is %d\n",value);
return;
}
int DeQueue(CyQueue *q,dataType *value)
{
if(q->front==q->rear)
{
printf("Cyclic Queue is Empty!\n");
return;
}
*value=q->base[q->front];
q->front=(q->front+1)%Maxsize;
printf("DeQueue Element is %d\n",*value);
return;
}
dataType getHead(CyQueue *q)
{
if(q->front==q->rear)
{
printf("Cyclic Queue is Empty! Unable to fetch Header of Cyclic Queue\n");
return;
}
return(q->base[q->front]);
}
int main()
{
CyQueue q;
dataType elem;
int i;
create(&q);
for(i=1;i<11;i++)
EnQueue(&q,i);
printf("The Header is %d\n",getHead(&q));
for(i=0;i<10;i++)
DeQueue(&q,&elem);
printf("The Header is %d\n",getHead(&q));
return 0;
}
执行结果:
问题探究 :
关于EnQueue中为什么可以用指针做数组名来访问数组( “q->base[q->front]”)做一个讨论。
- 首先,我们将问题化简一下:
int a[5]={1,2,3,4,5};
int *p;
p=a;//“ p=&a[0] ”;也可以
printf("a[3]=%d\n",a[3]);
printf("p[3]=%d\n",p[3]);
让指针p去指向数组a,比较a[3]和p[3]输出的值,发现其相等。
- 其实此处关系到C语言编译器中如何理解数组的,也就是“语法糖”,a[3]编译器理解方式是
*(a+3)
;那么p[3]也会被编译器理解成*(p+3)
;也就是地址+偏移量,那么既然p=a都是数组首地址,所+的偏移量也相同,自然结果是一样的; - 那么在我们知道了编译器是如何理解数组的基础上我们可以发现更多有意思的表达式。
int a[5]={1,2,3,4,5};
int b=3;
printf("a[b]=%d\n",a[b]);
printf("b[a]=%d\n",b[a]);
输出a[b]的值我们都知道是a[3]==3;可是b[a]的值呢?根据上述,b[a]将会被理解成:*(b+a)
, 而a[b]则是:*(a+b)
,所以a[b]==b[a]==4 。
- 还有类似4[a]、(1+2) [a] 、(a+b)[b] 都可通过编译,并且正确输出
参考:
- 参考文献《C Primer Plus 6th》;
- 《C++中的古怪表达式》——paschen;