1.创建循环队列并初始化。
2.入队。
3.出队。
4.取对头元素。
为什么我们要使用循环队列?
- 普通顺序队列:
- 在使用普通顺序队列的时,判断队空的操作是rear==front,判断队满的操作是判断rear是否在队列尾部我们不难发现当图(b)与图 (d) 所示情况出现时,rear都处于队列尾部,即程序都将判断队列已满,但是图(d)显然有空,即会出现“真溢出”和“假溢出”的问题。
- 为了解决“假溢出”的问题,保证存储空间不浪费,故有了循环队列。
(注:循环队列中的指针rear和front并非真正的指针类型,而是int整型,它们用于记录数组的下标,我们形象化的称之为“指针”)
-
要实现循环队列,最重要的是如何将指向队列尾元素的指针rear转去指向队列首元素,我们可以采用**“模”运算**:rear=(rear+1)% Maxsize;当队列中的元素在“第一个循环前”,rear+1 < Maxsize(数组下标加一仍小于数组容量),故(rear+1)% Maxsize的值仍等于rear不变(对一个比自己大的数取余,余数仍等于自己本身);当队列中的元素开始“第一个循环时”,rear+1 ≥ Maxsize, 当rear+1=Maxsize时,(rear+1)% Maxsize==0 即对应了数组下标为0,即开始第一次循环。
-
因此“模”运算非常巧妙的将数组中循环的问题类比化成“取余”这一运算,从而解决问题。
-
循环队列可以高效的利用已出队元素的空间,但是新的问题又出现了,当队满时如图(d1)所示,此时rear==front表示队满;而空队列如图(a)所示,rear==front表示队列空,即无法判断队满还是队空。
-
此时我们可以采用两种办法解决,1.牺牲一个存储单元,如图(d2)所示,人为的默认图(d2)的状态为队满,此时队空的条件为:rear==front;队满的条件为:(rear+1)% Maxsize==front;2.可以设一个计数器,计当前队列中元素的个数,通过比较计数器的数值与数组的最大容量来判断队列状态。
#include <stdio.h>
#include <stdlib.h>
#define Maxsize 10 /*定义数组容量,需要更改时直接更改数值即可*/
typedef int dataType; /*定义元素类型,此处为int,需要更改时直接更改类型即可*/
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 push_in(CyQueue *q,dataType value)
{
if((q->rear+1)%Maxsize==q->front)
{
printf("Cyclic Queue is Full!\n");
return;
}
q->base[q->rear]=value; /*q->base为指向数组的指针类型,
关于用指针做数组名访问数组见注释1*/
q->rear=(q->rear+1)%Maxsize;
printf("Push Element is %d\n",value);
return;
}
int pop_out(CyQueue *q,dataType *value)/*此处“*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("Pop 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++)
push_in(&q,i);
printf("The Header is %d\n",getHead(&q));
for(i=0;i<10;i++)
pop_out(&q,&elem);
printf("The Header is %d\n",getHead(&q));
return 0;
}
执行结果:
注释:
- 关于push_in中为什么可以用指针做数组名来访问数组( “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;