目录
3.2.1 队列的定义
队列简称队,它也是一种运算受限的线性表。
队列只能选取一个端点进行插入操作,另一个端点进行删除操作。
队列的几个概念:
1、进行插入的一端称做队尾(rear)。
2、进行删除的一端称做队首或队头(front)。
3、向队列中插入新元素称为进队或入队,新元素进队后就成为新的队尾元素。
4、从队列中删除元素称为出队或离队,元素出队后,其后继元素就成为队首元素。
队列的主要特点是先进先出,所以又把队列称为先进先出表。
队列抽象数据类型=逻辑结构+基本运算(运算描述)
队列的基本运算如下:
InitQueue(&q):初始化队列。构造一个空队列q。
DestroyQueue(&q):销毁队列。释放队列q占用的存储空间。
QueueEmpty(q):判断队列是否为空。若队列q为空,则返回真;否则返回假。
enQueue(&q,e):进队列。将元素e进队作为队尾元素。
deQueue(&q,&e):出队列。从队列q中出队一个元素,并将其值赋给e。
3.2.2 队列的顺序存储结构及其基本运算实现
队列中元素逻辑关系与线性表的相同,队列可以采用与线性表相同的存储结构。
顺序队类型SqQueue声明如下:
(因为队列两端都在变化,所以需要两个指针来标识队列的状态)
typedef struct
{ ElemType data[MaxSize];
int front,rear; //队首和队尾指针
} SqQueue;
总结:
- 约定rear总是指向队尾元素
- 元素进队,rear增1
- 约定front指向当前队中队头元素的前一位置
- 元素出队,front增1
- 当rear=MaxSize-1时不能再进队
顺序队的4要素(初始时front=rear=-1):
- 队空条件:front = rear
- 队满条件:rear = MaxSize-1
- 元素e进队:rear++; data[rear]=e;
- 元素e出队:front++; e=data[front];
注意:rear指向队尾元素;front指向队头元素的前一个位置。
1、顺序队中实现队列的基本运算算法
(1)初始化队列InitQueue(q) 构造一个空队列q。将front和rear指针均设置成初始状态即-1值。
void InitQueue(SqQueue *&q)
{ q=(SqQueue *)malloc (sizeof(SqQueue));
q->front=q->rear=-1;
}
(2)销毁队列DestroyQueue(q) 释放队列q占用的存储空间。
void DestroyQueue(SqQueue *&q)
{
free(q);
}
(3)判断队列是否为空QueueEmpty(q) 若队列q满足q->front==q->rear条件,则返回true;否则返回false。
bool QueueEmpty(SqQueue *q)
{
return(q->front==q->rear);
}
(4)进队列enQueue(q,e) 在队列不满的条件下,先将队尾指针rear循环增1,然后将元素添加到该位置。
bool enQueue(SqQueue *&q,ElemType e)
{ if (q->rear==MaxSize-1) //队满上溢出
return false;
q->rear++;
q->data[q->rear]=e;
return true;
}
(5)出队列deQueue(q,e) 在队列q不为空的条件下,将队首指针front循环增1,并将该位置的元素值赋给e。
bool deQueue(SqQueue *&q,ElemType &e)
{ if (q->front==q->rear) //队空下溢出
return false;
q->front++;
e=q->data[q->front];
return true;
}
2、环形队列(或循环队列)中实现队列的基本运算
这是因为采用rear==MaxSize-1作为队满条件的缺陷。当队满条件为真时,队中可能还有若干空位置。这种溢出并不是真正的溢出,称为假溢出。
解决方案
把数组的前端和后端连接起来,形成一个环形的顺序表,即把存储队列元素的表从逻辑上看成一个环,称为环形队列或循环队列。
环形队列(循环队列):
实际上内存地址一定是连续的,不可能是环形的,这里是通过逻辑方式实现环形队列,也就是将rear++和front++改为:
- rear=(rear+1)%MaxSize
- front=(front+1)%MaxSize
现在约定rear=front为队空,以下两种情况都满足该条件:
环形队列的4要素:
- 队空条件:front = rear
- 队满条件:(rear+1)%MaxSize = front
- 进队e操作:rear=(rear+1)%MaxSize; 将e放在rear处
- 出队操作:front=(front+1)%MaxSize; 取出front处元素e;
在环形队列中,实现队列的基本运算算法与非环形队列类似,只是改为上述4要素即可。
3、相关例题
【例3.7】对于环形队列来说,如果知道队头指针和队列中元素个数,则可以计算出队尾指针。也就是说,可以用队列中元素个数代替队尾指针。
设计出这种环形队列的初始化、入队、出队和判空算法。
解
依题意设计的环形队列类型如下:
typedef struct
{ ElemType data[MaxSize];
int front; //队头指针
int count; //队列中元素个数
} QuType;
对应的算法如下:
void InitQueue(QuType *&qu) //初始化队运算算法
{ qu=(QuType *)malloc(sizeof(QuType));
qu->front=0;
qu->count=0;
}
bool EnQueue(QuType *&qu,ElemType x) //进队运算算法
{ int rear; //临时队尾指针
if (qu->count==MaxSize) //队满上溢出
return false;
else
{ rear=(qu->front+qu->count)%MaxSize; //求队尾位置
rear=(rear+1)%MaxSize; //队尾循环增1
qu->data[rear]=x;
qu->count++; //元素个数增1
return true;
}
}
bool DeQueue(QuType *&qu,ElemType &x) //出队运算算法
{ if (qu->count==0) //队空下溢出
return false;
else
{ qu->front=(qu->front+1)%MaxSize; //队头循环增1
x=qu->data[qu->front];
qu->count--; //元素个数减1
return true;
}
}
bool QueueEmpty(QuType *qu) //判队空运算算法
{
return(qu->count==0);
}
3.2.3 队列的链式存储结构及其基本运算的实现
单链表中数据结点类型DataNode声明如下:
typedef struct qnode { ElemType data; //数据元素 struct qnode *next; } DataNode;
链队结点类型LinkQuNode声明如下:
typedef struct { DataNode *front; //指向单链表队头结点 DataNode *rear; //指向单链表队尾结点 } LinkQuNode;
链队的4要素:
- 队空条件:front=rear=NULL
- 队满条件:不考虑
- 进队e操作:将包含e的结点插入到单链表表尾
- 出队操作:删除单链表首数据结点
在链队存储中,队列的基本运算算法:
(1)初始化队列InitQueue(q) 构造一个空队列,即只创建一个链队头结点,其front和rear域均置为NULL,不创建数据元素结点。
void InitQueue(LinkQuNode *&q)
{ q=(LinkQuNode *)malloc(sizeof(LinkQuNode));
q->front=q->rear=NULL;
}
(2)销毁队列DestroyQueue(q) 释放队列占用的存储空间,包括链队头结点和所有数据结点的存储空间。
void DestroyQueue(LinkQuNode *&q)
{ DataNode *p=q->front,*r; //p指向队头数据结点
if (p!=NULL) //释放数据结点占用空间
{ r=p->next;
while (r!=NULL)
{ free(p);
p=r;r=p->next;
}
}
free(p); free(q); //释放链队结点占用空间
}
(3)判断队列是否为空QueueEmpty(q) 若链队结点的rear域值为NULL,表示队列为空,返回true;否则返回false。
bool QueueEmpty(LinkQuNode *q)
{
return(q->rear==NULL);
}
(4)进队enQueue(q,e)
考虑情况:
- 原队列为空
- 原队列非空
void enQueue(LinkQuNode *&q,ElemType e)
{ DataNode *p;
p=(DataNode *)malloc(sizeof(DataNode));
p->data=e;
p->next=NULL;
if (q->rear==NULL) //若链队为空,新结点是队首结点又是队尾结点
q->front=q->rear=p;
else
{ q->rear->next=p; //将p结点链到队尾,并将rear指向它
q->rear=p;
}
}
(5)出队deQueue(q,e)
考虑情况:
- 原队列为空
- 原队列只有一个结点
- 其他情况
bool deQueue(LinkQuNode *&q,ElemType &e)
{ DataNode *t;
if (q->rear==NULL) return false; //队列为空
t=q->front; //t指向第一个数据结点
if (q->front==q->rear) //队列中只有一个结点时
q->front=q->rear=NULL;
else //队列中有多个结点时
q->front=q->front->next;
e=t->data;
free(t);
return true;
}
【例3.8】 采用一个不带头结点只有一个尾结点指针rear的循环单链表存储队列,设计队列的初始化、进队和出队等算法。
链队的4要素:
- 队空条件:rear=NULL
- 队满条件:不考虑
- 进队e操作:将包含e的结点插入到单链表表尾
- 出队操作:删除单链表首结点
void InitQueue(LinkList *&rear) //初始化队运算算法
{
rear=NULL;
}
bool queueEmpty(LinkList *rear) //判队空运算算法
{
return(rear==NULL);
}
void enQueue(LinkList *&rear,ElemType x) //进队运算算法
{ LinkList *p;
p=(LinkList *)malloc(sizeof(LinkList)); //创建新结点
p->data=x;
if (rear==NULL) //原链队为空
{ p->next=p; //构成循环链表
rear=p;
}
else
{ p->next=rear->next; //将p结点插入rear结点之后
rear->next=p;
rear=p; //让rear指向新插入的结点
}
}
bool deQueue(LinkList *&rear,ElemType &x) //出队运算算法
{ LinkList *q;
if (rear==NULL) return false; //队空
else if (rear->next==rear) //原队只有一个结点
{ x=rear->data;
free(rear);
rear=NULL;
}
else //原队有两个或以上的结点
{ q=rear->next;
x=q->data;
rear->next=q->next;
free(q);
}
return true;
}
3.2.4 队列的应用
1. 求解报数问题
问题描述
设有n个人站成一排,从左向右的编号分别为1~n,现在从左往右报数“1,2,1,2,…”,数到“1”的人出列,数到“2”的立即站到队伍的最右端。报数过程反复进行,直到n个人都出列为止。要求给出他们的出列顺序。
例如,当n=8时,初始序列为 1 2 3 4 5 6 7 8
则出列顺序为 1 3 5 7 2 6 4 8
运算算法设计
算法思想:先将n个人的编号进队,然后反复执行如下操作,直到队列为空:
1、出队一个的元素,输出其编号—报数为1的人出列。
2、若队列不空,则再出队一个元素,并将刚出列的元素进队—报数为2的人站到队伍的最右端即队尾。
void number(int n)
{ int i; ElemType e;
SqQueue *q; //环形队列指针q
InitQueue(q); //初始化队列q
for (i=1;i<=n;i++) //构建初始序列
enQueue(q,i);
printf("报数出列顺序:");
while (!QueueEmpty(q)) //队列不空循环
{ deQueue(q,e); //出队一个元素e
printf("%d ",e); //输出元素编号
if (!QueueEmpty(q)) //队列不空
{ deQueue(q,e); //出队一个元素e
enQueue(q,e); //将刚出列的元素进队
}
}
printf("\n");
DestroyQueue(q); //销毁队列q
}
设计求解程序
int main()
{ int i,n=8;
printf("初始序列:");
for (i=1;i<=n;i++)
printf("%d ",i);
printf("\n");
number(n);
return 1;
}
2. 求解迷宫问题
数据组织
使用一个队列qu记录试探的方块,该队列的结构如下:
typedef struct
{ int i,j; //方块的位置
int pre //本路径中上一方块在队列中的下标
} Box; //方块类型
typedef struct
{ Box data[MaxSize];
int front,rear; //队头指针和队尾指针
} QuType; //定义顺序队类型
使用的队列qu不是环形队列(因为要利用出队的元素找路径),因此在出队时,不会将出队元素真正从队列中删除,因为要利用它输出路径。
用队列求一条迷宫路径的算法:(xi,yi)→ (xe,ye)
bool mgpath1(int xi,int yi,int xe,int ye)
//搜索路径为:(xi,yi) (xe,ye)
{ Box e;
int i,j,di,i1,j1;
QuType *qu; //定义顺序队指针qu
InitQueue(qu); //初始化队列qu
e.i=xi; e.j=yi; e.pre=-1;
enQueue(qu,e); //(xi,yi)进队
mg[xi][yi]=-1; //将其赋值-1,以避免回过来重复搜索
while (!QueueEmpty(qu)) //队不空循环
{ deQueue(qu,e); //出队方块e
i=e.i; j=e.j;
if (i==xe && j==ye) //找到了出口,输出路径
{ dispapath(qu,qu->front); //调用dispapath函数输出路径
DestroyQueue(qu); //销毁队列
return true; //找到一条路径时返回真
}
for (di=0;di<4;di++) //循环扫描每个方位
{ switch(di)
{
case 0:i1=i-1; j1=j; break;
case 1:i1=i; j1=j+1; break;
case 2:i1=i+1; j1=j; break;
case 3:i1=i; j1=j-1; break;
}
if (mg[i1][j1]==0)
{ e.i=i1; e.j=j1;
e.pre=qu->front;
enQueue(qu,e); //(i1,j1)方块进队
mg[i1][j1]=-1; //将其赋值-1
}
}
}
DestroyQueue(qu); //销毁队列
return false;
}
3.2.5 双端队列
所谓双端队列是指两端都可以进行进队和出队操作的队列,将队列的两端分别称为前端和后端,两端都可以入队和出队。 其元素的逻辑结构仍是线性结构。
【例3.10】某队列允许在两端进行入队操作,但仅允许在一端进行出队操作,若a、b、c、d、e元素进队,则不可能得到的顺序是( )。
A. bacde B. dbace C. dbcae D. ecbad
答案为C。
本章完。