2、队列
2.1 队列的基本概念
队列:队列简称队,是一种操作受限的线性表,只允许在表的一端进行插入,而在表的另一端进行删除。向队列种插入元素称为入队或进队;删除元素称为出队或离队。队列的示意图如下所示:
对头(front):允许删除的一端,又称队首。
队尾(rear):允许插入的一端。
空队列:不含任何元素的空表。
队列的特性:先进先出。
队列的应用:速度不匹配问题、多用户资源竞争问题。
注意:栈和队列都是操作受限的线性表,不是任何对线性表的操作都适合栈和队列的操作,不可以随便读取栈或队列中间的某个元素。
2.2 队列的顺序存储结构
1.队列的顺序存储
队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:对头指针 front 指向对头元素,队尾指针 rear 指向队尾元素的下一个位置。
队列的顺序存储类型描述如下:
1 |
|
初始状态(队空条件):Q.front == Q.rear == 0
进队操作:队不满时,先送值到队尾元素,再将队尾指针加1
出队操作:队不空时,先取队头元素值,再将队头指针加1
队列的操作示意图如下图所示:

队列操作示意图中 d 图所示,队列中仅有一个元素,再进行入队操作时,会出现“上溢出”,但这种溢出不是真正的溢出,在队列数组中仍然存在可以存放元素的空位置,这是一种“假溢出”。
2.循环队列
循环队列:将顺序队列臆造成一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。
当队首指针 Q.front = MaxSize-1 后,再前进一个位置就会自动到 0,可利用除法取余(%)来实现。
初始时:Q.front = Q.rear = 0
队首指针进1:Q.front = (Q.front + 1)%MaxSize
队尾指针进1:Q.rear = (Q.rear + 1)%MaxSize
队列长度:(Q.rear + MaxSize - Q.front)%MaxSize
出队入队时:指针都按照顺时针方向进1
注意:不能用动态分配的一维数组来实现循环队列,初始化时必须设定一个最大队列长度。
为了区分循环队列队空还是队满情况,有三种处理方式,其中第一种为常用的区分方式,重点掌握:
(1)牺牲一个单元来区分队空还是队满,入队时少用一个队列单元,约定以“队头指针在队尾的下一个指针作为堆满标志”。如下图 (d2) 所示。
队满条件: (Q.rear + 1)%MaxSize == Q.front
队空条件:Q.front == Q.rear
队列中元素的个数:(Q.rear + MaxSize - Q.front)%MaxSize
(2)类型中增设表示元素个数的数据成员。对空的条件为 Q.size = 0,队满的条件为 Q.size == MaxSize,有Q.front == Q.rear。
(3)类型中增设 tag 数据成员,以区分是队空还是队满。tag = 0 时,若因删除导致 Q.front == Q.rear,则为队空;tag = 1 时,若因插入导致 Q.front == Q.rear,则为队满。
循环队列出入队示意图如下所示:

3 . 循环队列的操作
(1)初始化
1 | void InitQueue(SqQueue &Q){ |
(2)判队空
bool isEmpty(SqQueue Q){
if(Q.front == Q.rear) //队空条件
return true;
else
return false;
}
(3)入队
bool EnQueue(SqQueue &Q,ElemType x){
if( (Q.rear + 1)%MaxSize == Q.front) //队满则报错
return false;
Q.data[Q.rear]=x;
Q.rear= (Q.rear + 1)%MaxSize; //队尾指针加1模
return true;
}
(4)出队
bool DeQueue(SqQueue &Q,ElemType &x){
if(Q.front == Q.rear) //队空则报错
return false;
x=Q.data[Q.front];
Q.front = (Q.front + 1)%MaxSize; //队头指针加1取模
return true;
}
- 顺序队列示例代码
顺序队列基本操作:顺序队列的初始化、入队列、出队列以及显示队列中的数据元素的示例代码如下所示:
1 |
|
运行结果如下图所示:
2.3 队列的链式存储结构
1.队列的链式存储
队列的链式表示称为链队列,实际上是一个同时带有队头指针和队尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点。
队列的不带头结点的链式存储示意图如下图所示:
队列的链式存储类型代码描述如下所示:
1 | typedef struct{ //链式队列结点 |
当 Q.front == NULL 且 Q.rear == NULL 时,链式队列为空。
出队时,首先判断队是否为空,若不空,则取出队头元素,将其从链表中移除,并让 Q.front 指向下一个结点(若该结点为最后一个结点,则令 Q.front 和 Q.rear 都为 NULL)。入队时,建立一个新结点,将新结点插入到链表的尾部,并改让 Q.rear 指向这个新插入的结点(若原队列为空队,则另 Q.front 也指向该结点)。
由于不带头结点的链式队列在操作上比较麻烦,因此通常将链式队列设计成一个带头结点的单链表,实现插入和删除相统一。带头结点和不带头结点的链式队列如下图所示:
优点:用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满且产生溢出的问题。
2.链式队列的基本操作
(1)初始化
void InitQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));//建立头结点
Q.front->next =NULL; //初始为空
(2)判队空
bool IsEmpty(LinkQueue Q){
if(Q.front == Q.rear)
return true;
else
return false;
}
(3)入队
void EnQueue(LinkQueue &Q,ElemTyepe x){
LinkNode *s=(LinkNode *)malloc(sizeof(LinkNode));
s->data=x; //创建新结点,插入到链尾
s->next=null;
Q.rear->next=s;
Q.rear=s;
}
(4)出队
bool DeQueue(LinkQueue &Q,ElemType &x){
if(Q.front==Q.rear) //空队
return false;
LinkNode *p =Q.front->next;
x=p->data;
Q.front->next=p->next;
if(Q.rear==p)
Q.rear=Q.front;//若原队列中只有一个结点,删除后变空
free(p);
return true;
}
3.链队列示例代码
链队列的操作:链队列的初始化、入链队列、链队列队头元素出链队列以及显示链队列所有数据元素的示例代码如下所示:
1 |
|
运行结果:
