一、队列的定义
和栈相反,队列说一种先进先出(缩写为FIFO)的线性表。它只允许在表的一端进行插入,在另一端除元素。在队列中,允许插入的一端叫队尾,允许删除的一端则成为队头。
还有一种限定性的数据结构说双端队列。双端队列是限定插入和删除操作中表的两端进行的线性表。
队列的常用操作包括:
- 初始化队列:创建一个队列。
- 入队:将一个元素添加到队尾。
- 出队:将一队头的元素去除,同时删除该元素,使下一个元素称为队头。
- 获取队列的第1个元素:将队头的元素取出,不删除该元素(队头仍然是该元素)。
- 获取队列长度:根据队头和队尾计算出队列中元素的数量。
二、队列的顺序存储表示
1.顺序队列
顺序队列通常采用一维数组一依次存放从队头到队尾的元素。同时,使用两个指针分别指示数组中存放的某一个元素和最后一个元素的位置。其中指向第一个元素的指针被称为队头指针front,指向最后一个元素的指针被称为队尾指针rear。
在使用队列前,先初始化队列,此时队列为空,队头指针front和队尾指针rear都指向队列的第一个位置,即front=rear=0。
每一个元素进入队列,队尾指针rear会增1。
每一个元素出队列时,队头指针front会增1。
假溢出:在对顺序队列进行插入和删除操作的过程中,可能会出现“假溢出”现象。经过多次插入和删除操作后,实际上队列还有存储空间,但是又无法向队列中插入元素,我们将这种溢出称为“假溢出”。
2.顺序循环队列
为了充分利用存储空间,消除这种“假溢出”现象,当队尾指针rear和队头指针front到达存储空间的最大值的时候,让队尾指针和队头指针转化为0,这样就可以把元素插入到队列还没有利用的存储单元中。这样就可以把顺序队列使用的存储空间构造成一个逻辑上首尾相连的循环队列,可以通过取余操作实现队列的逻辑上的首尾相连。
初始化建空队列时,令front=rear=0,每当插入新的队列元素时,尾指针增1;每当删除队列头元素时,头指针增。因此,在非空队列中,头指针始终指向队列头元素,而尾指针时钟指向队列尾元素的下一个位置。
仅凭等式Q.front=Q.rear无法判别队列空间时空还是满。可有两种处理方法:其一时另设一个标志位以区别队列时空还是满;其二是少用一个元素空间,约定“队列头指针在队列尾指针的下一位置上”作为队列呈“满”状态的标志。
如果用户的应用程序中设有循环队列,则必须为它设定一个最大队列长度;若用户无法预估所用的队列的最大长度,则宜采用链队列。
常用的确定队首位置,队尾位置,队列长度的语句:
Q.front=(Q.front+1)%MAXQSIZE;
Q.rear=(Q.rear+1)%MAXQSIZE;
(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE;
三、队列的顺序存储实现
1.顺序队列的实现
- 类型定义
#define QueueSize 12
typedef struct Squeue{ /*顺序队列类型定义*/
DataType queue[QueueSize];
int front,rear; /*队头指针和队尾指针*/
}SeqQueue;
- 初始化队列
void InitQueue(SeqQueue *SQ)
/*将顺序队列初始化为空队列只需要把队头指针和队尾指针同时置为0*/
{
SQ->front=SQ->rear=0; /*把队头指针和队尾指针同时置为0*/
}
- 队列状态
int QueueEmpty(SeqQueue SQ)
/*判断队列是否为空,队列为空返回1,否则返回0*/
{
if(SQ.front==SQ.rear) /*判断队头指针和队尾指针是否相等*/
return 1; /*当队列为空时,返回1;否则返回0*/
else
return 0;
}
- 入队函数
int EnQueue(SeqQueue *SQ,DataType x)
/*将元素x插入到顺序队列SQ中,插入成功返回1,否则返回0*/
{
if(SQ->rear==QueueSize) /*在插入新的元素之前,判断队尾指针是否到达数组的最大值,即是否队列已满*/
return 0;
SQ->queue[SQ->rear]=x; /*在队尾插入元素x */
SQ->rear=SQ->rear+1; /*队尾指针向后移动一个位置*/
return 1;
}
- 出队函数
int DeQueue(SeqQueue *SQ,DataType *e)
/*删除顺序队列中的队头元素,并将该元素赋值给e,删除成功返回1,否则返回0*/
{
if(SQ->front==SQ->rear) /*在删除元素之前,判断队列是否为空*/
return 0;
else
{
*e=SQ->queue[SQ->front]; /*将要删除的元素赋值给e*/
SQ->front=SQ->front+1; /*将队头指针向后移动一个位置,指向新的队头*/
return 1;
}
}
- 取队头元素
int GetHead (SeqQueue SQ,DataType *e)
/*取队头元素,并将该元素赋值给e,取元素成功返回1,否则返回0*/
{
if(SQ.front==SQ.rear) /*在取队头元素之前,判断顺序循环队列是否为空*/
return 0;
else
{
*e=SCQ.queue[SQ.front]; /*将队头元素赋值给e,取出队头元素*/
return 1;
}
}
- 清空队列
int ClearQueue(SeqQueue *SQ)
/*清空队列*/
{
SQ->front=SQ->rear=0; /*将队头指针和队尾指针都置为0*/
}
- 打印队列
void PrintQueue(SeqQueue SQ)
{
int cur;
DataType e;
printf("当前队列为:\n");
cur=SQ.front;
while(cur!=SQ.rear)
{
e=SQ.queue[cur++]; /*将队头元素赋值给e,取出队头元素*/
printf("%c ",e);
}
printf("\n");
}
- 主程序
void main()
{
int i,a;
DataType p[12],e;
SeqQueue Q;
InitQueue(&Q);
while(1)
{
printf("请选择操作:1.入队 2.出队 3.退出\n");
scanf("%d",&a);
getchar();
switch(a)
{
case 1:
if(Q.rear<QueueSize)
{
printf("请输入入队元素:\n");
scanf("%c",&e);
EnQueue(&Q,e);
PrintQueue(Q);
}
else
{
printf("队列已满!\n");
}
break;
case 2:
if(!QueueEmpty(Q))
{
DeQueue(&Q,&e);
printf("出队元素是:%c\n",e);
PrintQueue(Q);
}
break;
case 3:
default:
printf("程序结束!");
exit(1);
}
}
}
- 测试结果
2.顺序循环队列的实现
- 类型定义
#define QueueSize 12
typedef struct Squeue{ /*顺序队列类型定义*/
DataType queue[QueueSize];
int front,rear; /*队头指针和队尾指针*/
}SeqQueue;
- 初始化队列
void InitQueue(SeqQueue *SQ)
/*将顺序队列初始化为空队列只需要把队头指针和队尾指针同时置为0*/
{
SQ->front=SQ->rear=0; /*把队头指针和队尾指针同时置为0*/
}
- 队列状态
int QueueEmpty(SeqQueue SQ)
/*判断队列是否为空,队列为空返回1,否则返回0*/
{
if(SQ.front==SQ.rear) /*判断队头指针和队尾指针是否相等*/
return 1; /*当队列为空时,返回1;否则返回0*/
else
return 0;
}
- 入队函数
int EnQueue(SeqQueue *SQ,DataType e)
/*将元素x插入到顺序队列SQ中,插入成功返回1,否则返回0*/
{
if(SQ->front==(SQ->rear+1)%QueueSize) /*在插入新的元素之前,判断队尾指针是否到达数组的最大值,即是否队列已满*/
return 0;
SQ->queue[SQ->rear]=e; /*在队尾插入元素x */
SQ->rear=(SQ->rear+1)%QueueSize; /*队尾指针向后移动一个位置*/
return 1;
}
- 出队函数
int DeQueue(SeqQueue *SQ,DataType *e)
/*删除顺序队列中的队头元素,并将该元素赋值给e,删除成功返回1,否则返回0*/
{
if(SQ->front==SQ->rear) /*在删除元素之前,判断队列是否为空*/
return 0;
else
{
*e=SQ->queue[SQ->front]; /*将要删除的元素赋值给e*/
SQ->front=(SQ->front+1)%QueueSize; /*将队头指针向后移动一个位置,指向新的队头*/
return 1;
}
}
- 取队头元素
int GetHead (SeqQueue SCQ,DataType *e)
/*取队头元素,并将该元素赋值给e,取元素成功返回1,否则返回0*/
{
if(SCQ.front==SCQ.rear) /*在取队头元素之前,判断顺序循环队列是否为空*/
return 0;
else
{
*e=SCQ.queue[SCQ.front]; /*将队头元素赋值给e,取出队头元素*/
return 1;
}
}
- 清空队列
int ClearQueue(SeqQueue *SCQ)
/*清空队列*/
{
SCQ->front=SCQ->rear=0; /*将队头指针和队尾指针都置为0*/
}
- 显示队列
void DisplayQueue(SeqQueue SQ)
/*顺序循环队列的显示输出函数。首先判断队列是否为空,输出时还应考虑队头指针和队尾指针值的大小问题*/
{
int i;
if(QueueEmpty(SQ)) /*判断顺序循环队列是否为空*/
return 0;
if(SQ.front<SQ.rear)
/*如果队头指针值小于队尾指针的值,则把队头指针到队尾指针指向的元素依次输出*/
for(i=SQ.front;i<SQ.rear;i++)
printf("%c ",SQ.queue[i]);
else
/*如果队头指针值大于队尾指针的值,则把队尾指针到队头指针指向的元素依次输出*/
for(i=SQ.front;i<SQ.rear+QueueSize;i++)
printf("%c ",SQ.queue[i%QueueSize]);
printf("\n");
}
- 主程序
void main()
{
SeqQueue Q; /*定义一个顺序循环队列*/
char e; /*定义一个字符类型变量,用于存放出队列的元素*/
InitQueue(&Q); /*初始化顺序循环队列*/
/*将3个元素A,B,C依次进入顺序循环队列*/
printf("A入队\n");
EnQueue(&Q,'A');
printf("B入队\n");
EnQueue(&Q,'B');
printf("C入队\n");
EnQueue(&Q,'C');
/*将顺序循环队列中的元素显示输出*/
printf("队列中元素:");
DisplayQueue(Q);
/*将顺序循环队列中的队头元素出队列*/
printf("队头元素第一次出队\n");
DeQueue(&Q,&e);
printf("出队的元素:");
printf("%c\n",e);
printf("队头元素第二次出队\n");
DeQueue(&Q,&e);
printf("出队的元素:");
printf("%c\n",e);
/*将顺序循环队列中的元素显示输出*/
printf("队列中元素:");
DisplayQueue(Q);
/*将3个元素D,E,F依次进入顺序循环队列*/
printf("D入队\n");
EnQueue(&Q,'D');
printf("E入队\n");
EnQueue(&Q,'E');
printf("F入队\n");
EnQueue(&Q,'F');
/*将顺序循环队列中的元素显示输出*/
printf("队列中元素:");
DisplayQueue(Q);
}
- 测试结果
3.顺序循环队列的应用
详情参加博文【练习】舞伴配对问题