目录
一. 基本术语
1.定义:只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表(头删尾插)。
2.逻辑结构:与线性表相同,仍为一对一关系。
3.存储结构:顺序队或链队,以循环顺序队列更常见。
4.运算规则:只能在队首Front和队尾Rear运算,且访问结点时依照先进先出(FIFO)的原则。
5.实现方式:关键是掌握插入(入队)和删除(出队)操作,具体实现依顺序队或链队的不同而不同。
二. 案例引入
假设在舞会上,男士和女士各自排成一队。舞会开始时依次从男队和女队的队头各出一人配成舞伴。如果两队初始人数不相同,则较长的那一队中未配对者等待下轮舞曲。现要求写一算法模拟上述舞伴配对问题。
【算法步骤】显然,先入队的男士或女士先出队配成舞伴。因此该问题具有典型的先进先出特性,可以用队列作为算法的数据结构。
(1)首先构造两个队列QM,QF;
(2)依次将队头元素出队配成舞伴;
(3)某队为空,则另外一队等待者则是下一舞曲第一个可获得舞伴的人。
三. 队列的表示与实现
描述队列的抽象数据类型(ADT):
ADT Queue{
数据对象:D={ ai | ai ∈ElemSet, i=1,2..,n, n≥0 }
数据关系:R1= { <ai-1, ai >| ai-1, ai∈D, i=2,...,n }
//约定an端为队尾,a1端为队头。
基本操作:
InitQueue(&Q) //操作结果:构造空队列Q
DestroyQueue(&Q) //条件:队列Q已存在;操作结果:队列Q被销毁
ClearQueue(&Q) //条件:队列Q已存在;操作结果:将Q清空
QueueLength(Q) //条件:队列Q已存在;操作结果:返回Q的元素个数,即队长
GetHead(Q,&e) //条件:Q为非空队列;操作结果:用e返回Q的队头元素
EnQueue(&Q, e) //条件:队列Q已存在操作结果:插入元素e为Q的队尾元素
DeQueue(&Q,&e) //条件:Q为非空队列;操作结果:删除Q的队头元素,用e返回值
.....还有将队列置空、遍历队列等操作.....
}ADT Queue
队列也有两种存储方式:顺序存储与链式存储
(1)队列的顺序表示,循环顺序队列
队列的顺序表示-用一维数组base[MAXQSIZE],结构体定义如下:
# define MAXQSIZE 100 //最大队列长度
typedef struct{
QElemType *base; //初始化的动态分配存储空间
int front; //头指针
int rear; //尾指针
}SqQueue;
和栈一样,这里我们仍然认为尾指针指向队尾元素的下一个位置。
初始,空队列下:front=rear=0;
入队:base[rear]=x;rear++;
出队:x=base[front];front++;空队标志:front==rear;
真溢出(front=0,rear=MAXQSIZE,下左)与假溢出(front>0,rear=MAXQSIZE,下右):
为了避免假溢出,引入循环队列,将队空间设想成一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为MAXQSIZE时,若向量的开始端空着,又可从头使用空着的空间。当front为MAXQSIZE时,
具体的说,把base[0]接在base[MAXQSIZE-1]之后,若rear+1==M,则令rear=0;实现方法:利用模(mod,C语言中:%)运算。
因此,插入元素和删除元素的代码可以写成以下形式:
//插入队列元素
Q.base[Q.rear]=x;
Q.rear=(Q.rear+1)% MAXQSIZE;
//删除队列元素
x=Q.base[s.front];
Q.front=(Q.front+1)% MAXQSIZE;
但是此时队空和队满都有front==rear:
解决方案:
- 另外设一个标志以区别队空、队满
- 另设一个变量,记录元素个数
- 少用一个元素空间
我们介绍一下第三种方法:少用一个元素空间,当队列为空时仍然有front==rear;当队列为满时,队尾指针再+1,就能和队头指针重合。
1)循环队列的类型定义
# define MAXQSIZE 100
typedef struct{
QElemType *base; //动态分配存储空间
int front; //头指针,若队列不空指向第一个元素
int rear; //尾指针,若队列不空指向队列尾元素的下一个位置
}SqQueue;
2)循环队列的初始化
Status InitQueue(SqQueue &Q){ //构造一个空队列
Q.base=new QElemType[MAXSIZE];
//或Q.base=(QElemType*)malloc(MAXSIZE*sizeof(QElemType));
if(!Q.base) exit(OVERFLOW); //存储分配失败
Q.front=Q.rear=0; //队头指针等于队尾指针并且置零
return OK;
}
3)求循环队列的长度
int QueueLength(SqQueue Q){
return((Q.rear-Q.front+MAXQSIZE)%MAXQSIZE);
}
4)循环队列入队
Status EnQueue(SqQueue &Q,QElemType e){
if((Q.rear+1)%MAXQSIZE==Q.front) return ERROR; //队满
Q.base[Q.rear]=e; //插入队列
Q.rear=(Q.rear+1)%MAXQSIZE; //队尾指针+1
return OK;
}
5)循环队列出队
Status DeQueue(SqQueue &Q,QElemType e){
if(Q.rear==Q.front) return ERROR; //队空
e=Q.base[Q.front]; //删除队头元素
Q.front=(Q.front+1)%MAXQSIZE; //队头指针+1
return OK;
}
6)取队头元素
SElemType GetHead(SqQueue Q){
if(Q.front!=Q.rear) //队列不为空
return Q.base[Q.front]; //返回队头元素的值,队头指针不变
}
(2)队列的链式表示与实现
链队列的结构体定义同链表,不同的地方是链式队列有头尾指针。因此,首先定义一个结构体QNode,表示队列中的节点。每个节点包含一个数据域data,用于存储节点的数据,以及一个指针域next,用于指向下一个节点。然后,定义了一个结构体LinkQueue,表示链式队列。每个链式队列包含一个队头指针front,用于指向队列的第一个节点,以及一个队尾指针rear,用于指向队列的最后一个节点。
typedef struct QNode
{
QElemType data; //结点的数据域
struct QNode *next; //结点的指针域
}QNode,*QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr rear; //队尾指针
}LinkQueue;
1)链队列的初始化(a)
Status InitQueue(LinkQueue &Q){
Q.front=Q.rear=(QueuePtr)malloc(sizeof(QNode)); //分配一个结点让头尾指针都指向它
if(!Q.front) exit(OVERFLOW);
Q.front->next=NULL; //结点next域置空
return OK;
}
2)链队列的销毁
Status DestroyQueue(LinkQueue &Q){
while(Q.front){
p=Q.front->next; //先标记下一个结点,这里p也可以直接用Q.rear
free(Q.front);
Q.front=p; //front指针指向下一个结点
}
return OK;
}
3)链队列入队(c)
Status EnQueue(LinkQueue &Q,QElemType e){
p=(QueuePtr)malloc(sizeof(QNode)); //建立新结点
if(!p) exit(OVERFLOW);
p->data=e;
p->next=NULL;
Q.rear->next=p; //队尾连接新结点
Q.rear=p;
return OK;
}
4)链队列出队(d)
Status DeQueue (LinkQueue &Q,QElemType &e){
if(Q.front==Qrear) return ERROR; //空队,报错
p=Q.front->next; //p指向被删除的结点
e=p->data;
Q.front->next=p->next; //修改头指针next域指向下一个队头元素
if(Q.rear==p) Q.rear=Q.front; //如果整个队列只有一个元素,这个元素被删掉以后还要修改尾指针
delete p;
return OK;
}
5)查看队头元素
Status GetHead(LinkQueue Q,QElemType &e){
if(Q.front==Q.rear) return ERROR;
e=Q.front->next->data;
return OK;
}