一、队列的特征
队列是线性结构中的一种特殊限定性的数据结构,它有以下特征:
- 1.线性结构的特征队列都有,关于线性结构的特征可以看文章# 01--线性表的顺序存储;
- 2.队列的特殊限定性特征是:
先进先出
;- 3.队列由
一个方向入队
,一个方向出队
。我们称入队的方向为队尾
,出队的方向队头
。
二、队列顺序存储分析
下面来介绍队列在顺序存储中的设计与实现,顺序存储
的特点是内存连续且有限
。结合队列的特征
和顺序存储的特点
,我们来分析一下队列如何实现顺序存储。
假设我们设计一个顺序存储的数组
来保存队列,如下图所示:
- 1.当队列为空时,队尾rear等于队头front为0;
- 2.将c1、c2、c3依次入队。此时队头front为0,队尾rear为3;
- 3.将c1 、c2出队。此时队头front移动到2的位置,队尾rear为3;
- 4.将c4 c5 c6 入队,随后将c3 c4出队,此时队头front为4,队尾rear为5。
通过上面的一系列操作,最终最大长度为6的队列的4、5位置保存了有效数据,队尾rear = 6-1为5,即数组存满了,但0~3位置其实是没有存储有效数据的。这种情况我们称之为假溢出
问题。
三、循环队列
可以使用循环队列来解决假溢出问题,如下图:
- 1.初始化一个队列,队尾rear等于队头front等于0;
- 2.a b c入队,队尾rear指向3;
- 3.a出队,队头front指向1;
- 4.d e f g 入队,此时队头front和队尾rear又指向了同一位置1;
通过上面的步骤得出,当队空
和队满
时,队头front和队尾rear都相等
,这样就无法判断是队空还是队满了。解决这个问题的办法是在队头和队尾之间永远空出一个
位置,牺牲一个存储单元来,这并不会影响对队列的存储空间有多大影响。
通过上面的分析,我们总结如下:
- 1.设计一个
循环队列
来实现队列的顺序存储
;- 2.在队尾和队头之间永远
空出一个
位置,方便判断队满
的情况;- 3.
队空
的判断条件是队头front等于队尾rear
;- 4.
队满
的判断条件是队头rear+1
对数组长度MAXSIZE
求模
等于队头front
;- 5.
循环队列
的实现在出队、入队修改
位置时,对位置模以
数组长度MAXSIZE
,即可保证存储空间的有效利用,不会出现假溢出
的问题了。
四、循环队列的设计
1.准备工作
定义一些状态值和类型重定义
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20 /* 存储空间初始分配量 */
typedef int Status;
typedef int QElemType; /* QElemType类型根据实际情况而定,这里假设为int */
队列设计
typedef struct
{
QElemType data[MAXSIZE];
int front; /* 头指针 */
int rear; /* 尾指针,若队列不空,指向队列尾元素的下一个位置 */
}SqQueue;
- 1.设计一个结构体,data数组为队列的顺序存储空间;
- 2.front指向
队头
,rear指向队尾
。
1、初始化
Status InitQueue(SqQueue *Q){
Q->front = 0;
Q->rear = 0;
return OK;
}
队列的初始状态时队列为空,队头front和队尾rear指向同一位置0。
2、清空
Status ClearQueue(SqQueue *Q){
Q->front = Q->rear = 0;
return OK;
}
队列的为空时:队头front等于队尾rear等于0。
3、队列判空
Status QueueEmpty(SqQueue Q){
//队空标记
if (Q.front == Q.rear)
return TRUE;
else
return FALSE;
}
队列的判空的条件:队头front等于队尾rear等于0。
4、队列的长度
int QueueLength(SqQueue Q){
return (Q.rear - Q.front + MAXSIZE)%MAXSIZE;
}
因为是
循环阶列
,队尾rear可能大于
也可能小于
队头front,所以要将rear-front+MAXSIZE
。
5.获取队头数据
Status GetHead(SqQueue Q,QElemType *e){
//队列已空
if (Q.front == Q.rear)
return ERROR;
*e = Q.data[Q.front];
return OK;
}
front指向队头,直接从data的front位置取出数据即可。
6.入队
Status EnQueue(SqQueue *Q,QElemType e){
//队列已满
if((Q->rear+1)%MAXSIZE == Q->front)
return ERROR;
//将元素e赋值给队尾
Q->data[Q->rear] = e;
//rear指针向后移动一位,若到最后则转到数组头部;
Q->rear = (Q->rear+1)%MAXSIZE;
return OK;
}
- 1.
队满
的判断条件:(rear+1)%MAXSIZE == front
;- 2.队尾指向的位置是没有有效数据的,所以入队时,可以先在rear位置赋值,再将rear+1;
- 3.因为是
循环阶队
,所以队尾rear+1后要模以
MAXSIZE。
7.出队
Status DeQueue(SqQueue *Q,QElemType *e){
//判断队列是否为空
if (Q->front == Q->rear) {
return ERROR;
}
//将队头元素赋值给e
*e = Q->data[Q->front];
//front 指针向后移动一位,若到最后则转到数组头部
Q->front = (Q->front+1)%MAXSIZE;
return OK;
}
- 1.队列
判空
的条件:front == rear
;- 2.队头的位置是有有效数据的,所以可以可以直接取值;
- 3.出队时,队头要向队尾靠近,所以队头和队尾的运动方向要一致;
- 4.因为是
循环队列
,所以队头front+1
后要模以
MAXSIZE。
8.遍历队列数据
Status QueueTraverse(SqQueue Q){
int i;
i = Q.front;
while ((i+Q.front) != Q.rear) {
printf("%d ",Q.data[i]);
i = (i+1)%MAXSIZE;
}
printf("\n");
return OK;
}
- 1.遍历时,从队头到队尾;
- 2.在因为是遍历,所以
不能
改变队列的原有
结构,所以要使用一个临时变量i从队头累加到队尾,结束条件是i+front == rear
;- 3.注意
循环队列
的位置改变后要模以
MAXSIZE,防止数组越界。
五、总结
队列的顺序存储实现要注意假溢出
问题和队满判断
问题,解决方案是使用循环队列
,在队头和队尾之间空出一个位置
方便队满的判断。队满和队空的判断如下:
- 1.判断队空: rear == front;
- 2.判断队满:(rear + 1)%MAXSIZE = front。
因为是循环队列
,所以当rear和front变化时,要模以MAXSIZE
得到最终的正确指向。