队列的操作特性
先进先出,后进后出。
队列类型的实现
链队列——链式映像
typedef struct QNode
{
QElemType data;
QNode *next;
}*QueuePtr;
struct LinkQueue
{
QueuePtr front,rear; // 队头、队尾指针
};
循环队列——顺序映像
- 用一组地址连续的存储单元依次存放从队头到队尾的元素。
- 附设两个指针front和rear分别指示队头元素的位置和队尾元素的下一个位置。
- 初始化建空队列时令front=rear=0,插入新的队尾元素时尾指针增加1,删除队头元素时头指针增加1。
普通的顺序队列会出现假上溢的情况。
因为在对队列的操作中,头尾指针只增加不减小,导致被删除元素的空间永远无法重新利用。尽管队列中实际的元素个数可能远远小于存储空间的规模,但仍不能做入队操作,该现象称为“假上溢”。
克服“假上溢”现象的方法是将顺序队列想象为一个首尾相接的圆环,称之为循环队列。
循环对列中无法通过Q.front=Q.rear来判断队列“空”还是“满”。解决此问题的方法至少有3种:
1. 另设一个标志位区别队列的“空”和“满”。
2. 使用一个计数器记录队列中元素的总数(也就是队列的长度)。
3. 少用一个元素的空间,约定以“队列头指针在队列尾指针的下一个位置(指环状的下一个位置)上”作为队列呈“满”状态的标志。
以下采用的是第3种方法。
循环队列的类型定义
#define MAXQSIZE 5 // 最大队列长度(对于循环队列,最大队列长度要减1)
struct SqQueue
{
QElemType *base; // 初始化的动态分配存储空间
int front; // 头指针,若队列不空,指向队列头元素
int rear; // 尾指针,若队列不空,指向队列尾元素的下一个位置
};
循环队列需要注意以下几点:
- 如果Q.front==Q.rear,则可以判断循环队列为空;
- 如果(Q.rear+1)%MAXQSIZE==Q.front,则可以判断循环队列为满。
- 无论是对循环队列进行插入或删除元素时,均可能涉及到尾指针或头指针的调整(非简单地对指针进行+1操作),即
Q.rear=(Q.rear+1)%MAXQSIZE或Q.front=(Q.front+1)%MAXQSIZE -
如何理解(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE即为循环队列的长度
- 当Q.rear>Q.front时,循环队列长度=Q.rear-Q.front;
- 当
Q.rear<Q.front 时,循环队列长度=(Q.rear-Q.front+MAXQSIZE); - 综合考虑以上两种情况,循环队列长度(任何情况下)=(Q.rear-Q.front+MAXQSIZE)%MAXQSIZE;
队列与循环队列
关于循环队列是存储结构还是逻辑结构是有争议的,我更倾向于以下观点:
队列(包括循环队列)是一个逻辑概念,而链表是一个存储概念。一个队列是否是循环队列,不取决于它将采用何种存储结构,根据实际的需要,循环队列可以采用顺序存储结构,也可以采用链式存储结构,包括采用循环链表作为存储结构。
数据通过栈和队列产生的序列有什么不同?
- 一串数据依次通过一个栈,并不能保证出栈的次序总是倒置的,可以产生多种出栈序列。
- 一串数据通过一个栈后的次序由每个数据之间的入栈、出栈操作序列决定,只有当所有数据“全部入栈后再全部出栈”才能使数据倒置。事实上,存在一种操作序列——“入栈、出栈、入栈、出栈…”可以使数据通过栈后仍然保持次序不变。
一串数据通过一个队列,只有一种出队列顺序,就是其入队列顺序。
几种特殊的队列:
- 双端队列:可以在双端进行插入和删除操作的线性表。
- 输入受限的双端队列:线性表的两端都可以输出数据元素,但是只能在一端输入数据元素。
- 输出受限的双端队列:线性表的两端都可以输入数据元素,但是只能在一端输出数据元素。