数据结构——第二章栈、队列:02队列
1.队列的类型定义:
ADT Queue
{
数据对象:D = {ai | ai ∈ ElemSet, i = 1, 2, ..., n, n >= 0}
数据关系:R1 = {<ai-1, ai> | ai-1, ai ∈ D, i = 2, ..., n}(约定其中a1端为队列头,an端为队列尾)
基本操作:
①InitQueue(&Q)——初始化队列
②DestroyQueue(&Q)——销毁队列
③QueueEmpty(Q)——判断队列是否为空
④QueueLength(Q)——求队列长度
⑤GetHead(Q, &elem)——获取队列首元素
⑥ClearQueue(&Q)——清空队列
⑦EnQueue(&Q, elem)——入队操作
⑧DeQueue(&Q, &elem)——出队操作
⑨QueueTravers(Q, visit())——遍历队列
} ADT Queue
2.队列类型的实现:
(1)链队列:链式映像
typedef struct QNode //结点类型
{
QElemType data;
struct QNode *next;
} QNode, *QueuePtr;
typedef struct
{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
} LinkQueue;
(2)循环队列:顺序映像
①初始化队列操作:
Status InitQueue(LinkQueue &Q) //构造一个空队列Q
{
Q.front = Q.rear = new Qnode;
if (!Q.front)
{
return OVERFLOW; //存储分配失败
}
Q.front->next = NULL;
return OK;
}
②入队操作:
Status EnQueue(LinkQueue &Q, QElemType elem) //插入元素elem为Q的新队尾元素
{
p = new QNode;
if (!p)
{
return OVERFLOW; //存储空间分配失败
}
p->data = elem;
p->next = NULL;
Q.rear->next = p;
Q.rear = p;
return OK;
}
③出队操作:
Status DeQueue(LinkQueue &Q, QElemType &elem) //若队列不空,则删除Q的队首元素,用elem返回其值,并返回OK;否则返回ERROR
{
if (Q.front == Q.rear)
{
return ERROR;
}
p = Q.front->next;
e = p->data;
Q.front->next = p->next;
if (Q.rear == p)
{
Q.rear = Q.front;
}
delete(p);
return OK;
}
3.循环队列——队列的顺序表示和实现
(1)队列的顺序存储结构中用一组地址连续的存储单元一次存放从队头到队尾的元素。
(2)设置两个指针front和rear分别指向队头元素和队尾元素的下一个位置。
(3)初始化建空队列时令front=rear=0,插入新的队尾元素时尾指针增1,删除队头元素时头指针增1。
(4)在对队列的操作中,头尾指针只增加不减小,导致被删除元素的空间永远无法重新利用。尽管队列中实际的元素个数可能远远小于存储空间的规模,但仍不能做入队列操作,该现象称为假上溢。
(5)克服假上溢现象的方法是将顺序队列想象为一个首尾相接的圆环,称之为循环队列。
4.循环队列中无法通过Q.front=Q.rear来判断队列空还是满。解决此问题的方法可以用以下两种:
(1)使用一个计数器记录队列中元素的总数(实际上是队列长度)。
(2)少用一个元素的空间,约定以队列头指针在队列尾指针(环状的下一个位置)上作为队列呈满状态的标志。
5.循环队列类型定义:
#define MAXQSIZE 100 //最大队列长度
typedef struct
{
QElemType* base; //动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针,若队列不空,指向队列尾元素的下一个位置
int queuesize;
} SqQueue;
①循环队列的初始化操作:
Status InitQueue(SqQueue &Q, int maxsize) //构造一个最大存储空间为maxsize的空循环队列Q
{
Q.base = new ElemType[maxsize];
if (!Q.base)
{
return OVERFLOW;
}
Q.queuesize = maxsize;
Q.front = Q.rear = 0;
return OK;
}
②循环队列的入队操作:
Status EnQueue(SqQueue &Q, ElemType elem) //插入元素elem为Q的新的队尾元素
{
if ((Q.rear + 1) % Q.queuesize == Q.front)
{
return ERROR; //队列满
}
Q.base[Q.rear] = elem;
Q.rear = (Q.rear + 1) % Q.queuesize;
return OK;
}
③循环队列的出队操作:
Status DeQueue(SqQueue &Q, ElemType &elem) //若队列不空,则删除Q的队头元素,用elem返回其值,并返回OK;否则返回ERROR
{
if (Q.front == Q.rear)
{
return ERROR;
}
e = Q.base[Q.front];
Q.front = (Q.front + 1) % Q.queuesize;
return OK;
}
6.如果(Q.rear + 1) % MAXQSIZE == Q.front,则可以判断循环队列为满。无论是对循环队列进行插入或删除元素时,均可能涉及到尾指针或头指针的调整(不是简单地对指针进行+1操作),即:Q.rear = (Q.rear + 1) % MAXQSIZE 或 Q.front = (Q.front + 1) % MAXQSIZE。
7.求循环队列长度:(Q.rear - Q.front + MAXQSIZE) % MAXQSIZE。
8.循环队列与循环链表:循环队列是一个逻辑概念,链表是一个存储概念。一个队列是否是循环队列,不取决于它使用什么存储结构,循环队列可以采用顺序存储结构,也可以用链式存储结构,包括采用循环链表作为存储结构。
9.几种特殊的队列:
(1)双端队列:可以在两端进行插入和删除操作的线性表。
(2)输入受限的双端队列:线性表的两端都可以输出数据元素,但是只能在一端输入数据元素
(3)输出受限的双端队列:线性表的两端都可以输入数据元素,但是只能在一端输出数据元素。