队列
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列的定义
队列(queue),和栈一样,也是一种对数据的“存”和“取”有严格要求的线性存储结构。是一种先进先出的线性表。在队列中,允许插入的一端称为队尾(rear),允许删除的一端称为对头(front)。
队的存储示意图:
与栈结构不同的是,队列的两端都"开口",要求数据只能从一端进,从另一端出
通常,称进数据的一端为 “队尾”,出数据的一端为 “队头”,数据元素进队列的过程称为 “入队”,出队列的过程称为 “出队”。
队列的抽象数据类型定义:
ADT Queue{
数据对象:D={ai | ai∈ElemSet,i=1,2,…,n,n≥0}
数据关系:R={< a(i-1) , ai > | a(i-1), ai ∈ D,i=2,…,n}
基本操作:
InitQueue(&Q)
// 构造一个空队列Q
DestroyQueue(&Q)
// 销毁队列Q
ClearQueue(&Q)
// 清空队列Q
QueueEmpty(Q)
// 若Q为空队列,返回true,否则返回false
QueueLength(Q)
// 返回Q的元素个数,即队列的长度
GetHead(Q)
// 返回Q的队头元素
EnQueue(&Q,e)
// 插入元素e为Q的新的队尾元素
DeQueue(&Q,&e)
// 删Queue除Q的队头元素,并用e返回其值
QueueTraverse(Q)
// 从队头到队尾,依次对Q的每个元素访问
}ADT Queue
队列的常见应用
- 脱机打印输出:按申请的先后顺序依次输出
- 多用户系统中,多个用户排成队,分时地循环使用CPU和主存
- 按用户地优先级排成多个队,每个优先级一个队列
- 实时控制系统中,信号按接受地先后顺序依次处理
- 网络电文传输,按到达的时间顺序依次进行
循环队列
顺序队列:在顺序表的基础上实现的队列结构。
循环队列的顺序表示和实现
//---------队列的顺序存储结构--------
#define MAXQSIZE 100 //最大队列长度
typedef struct{
QElemType *base; //初始化的动态分配存储空间
int front; //头指针,若队列不空,指向队列头元素
int rear; //尾指针 ,若队列不空,指向队列尾元素的下一个位置
}SqQueue;
为了在c语言中描述方便起见,在此约定:初始化创建空队列时,令front=rear=0,每当插入新的队列尾元素时,尾指针rear增1;每当删除队列头元素时,头指针front增1。因此,在非空队列中,头指针始终指向队列头元素,而尾指针始终指向队列尾元素的下一个位置,如图:
假设当前队列分配的最大空间为6,则当队列处于图4所示的状态时不可再继续擦插入新的队尾元素,否则会出现溢出现象,即因数组越界而导致程序的非法操作错误。事实上此时的队列实际可用空间并未占满,所以称为这种现象为“假溢出”现象。这是由“队尾入队,队头出队”这种受限制的操作造成的。
解决假上溢的方法
1.将队列元素依次向队头方向移动
缺点:浪费时间。没移动一次,队中元素都要移动一次。
2.将队空间设想成一个循环的表,即分配给队列的m个存储单元可以循环使用,当rear为maxqsise时,若向量的开始端空着,又可从头使用空着的空间。当front为maxqsize时,也是一样。
这就是引入循环队列,将其队列想象成环状的空间。
头、尾指针以及队列元素之间的关系不变,只是在循环队列中,头、尾指针“依环状增1”的操作可用“模”运算来实现。通过取模【Q.rear = (Q.rear + 1)%MAXQSIZE】,头指针和尾指针就可以在顺序表空间内以头尾衔接的方式“循环”移动。
可是这样操作后会存在这样一个情况,如图:
由图(d)可知道若出现图(d1)情况,rear = front,导致无法判断队满还是队空;
由此可见,对于循环队列不能以头、尾指针的值是否相同来判别队列空间是“满”还是“空”。
-
处理办法:
少用一个元素空间,即队列空间大小为m时,有m-1个元素就认为是队满。这样判断队空的条件不变(即头尾指针的值相同),则为队空;而当尾指针在循环意义上加1后是等于头指针,则认为是队满。
队空的条件:Q.front == Q.rear
队满的条件:(Q.rear+1)%MAXQSIZE==Q.front
插入元素:Q.base[Q.rear]=x;
Q.rear=(Q.rear+1)%MAXQSIZE;删除元素:x=Q.base[s.front];
Q.front=(Q.front+1)%MAXQSIZE;
循环队列的初始化
- 循环队列的初始化
//队列的初始化
Status InitQueue(SqQueue &Q){
Q.base = new QElemType[MAXQSIZE]; //分配数组空间
//Q.base = (QElemType*)malloc(MAXQSIZE*sizeof(QElemType));
if(!Q.base) exit(OVERFLOW); //存储分配失败
Q.front = Q.rear = 0; //头指针尾指针置为0,队列为空
return OK;
}
循环队列的长度
对于非循环队列,尾指针和头指针的差值便是队列长度,而对于循环队列,差值可能是负数,所以需要将差值加上MAXQSIZE,然后与MAXQSIZE求余。
/队列长度
int QueueLength(SqQueue Q)
{ //返回Q的元素个数,即队列的长度
return(Q.rear - Q.front +MAXQSIZE) %MAXQSIZE;
}
循环队列的入队
- 循环队列的入队
1.判断队列是否满,若满则返回ERROR
2.将新元素插入队尾
3.队尾指针加1
//循环队列入队
Status EnQueue(SqQueue &Q,QElemType e){
if((Q.rear + 1)%MAXQSIZE == Q.front)//队满
return ERROR;
Q.base[Q.rear] = e; //新元素加入队尾
Q.base = (Q.base + 1)% MAXQSIZE; //队尾指针+1
return OK;
}
循环队列的出队
- 循环队列的出队
1.判断队列是否为空,若空则返回ERROR
2.保存队头元素
3.队头指针加1
//循环队列出队
Status DeQueue( SqQueue &Q, QElemType &e){
if(Q.front == Q.rear) return ERROR; //队空
e = Q.base[Q.base]; //保存队头元素
Q.front = (Q.front + 1)%MAXQSIZE; //队头指针+1
return OK;
}
取队头元素
- 取队头元素
//取队头元素
SElemType GetHead(SqQueue Q){
if(Q.front != Q.rear) //队列不为空
return Q.basep[Q.front]; //返回对头指针元素的值,队头指针不变
}
由以上可知,如果用户的应用程序中设有循环队列,则必须为它设定一个最大队列长度;若用户无法预估所用队列的最大长度,则宜采用链队。
链队列
链队列:在链表的基础上实现的队列结构。
链式队列的实现思想同顺序队列类似,只需创建两个指针(命名为 top 和 rear)分别指向链表中队列的队头元素和队尾元素,如图 1 所示:
上图所示为链式队列的初始状态,此时队列中没有存储任何数据元素,因此top和rear指针都同时指向头结点。
队列的链式表示和实现
- 队列的链式存储结构:
//------队列的链式存储结构------
typedef struct QNode{
QElemType data;
struct Qnode *next;
}QNode,*QueuePtr;
typedef struct{
QueuePtr front; //队头指针
QueuePtr raer; //队尾指针
}LinkQueue;
链队的操作几位单链表插入和删除操作的特殊情况,只需进一步修改尾指针或头指针。
链队的初始化
- 初始化
1.生成新结点作为头结点,队头和队尾指针指向此结点
2.头结点的指针域置空
//链队列初始化
Status InitQueue(LinkQueue &Q){
Q.front = Q.rear = new QNode; //生成新结点作为头指针,队头和队尾指针指向此结点
if(!Q.front) exit(OVERFLOW);
Q.front->next = NULL; //头结点的指针置空
return OK;
}
链队的基本操作
- 入队
1.为入队元素分配结点空间,用指针p指向
2.将新结点数据置为e
3.将新结点插入到队尾
4.修改队尾指针
//将元素e入队
Status EnQueue(LinkQueue &Q,QElemType e){
p = new QNode; //为入队元素分配结点空间,用指针p指向
if(!p) exit(OVERFLOW);
p->data = e; //将新结点数据置为e
p->next = NULL;Q.rear->next =p; //将新结点插入到队尾
Q.rear = p; //修改队尾指针
return OK;
}
- 出队
1.判断队列是否为空,若空则返回ERROR
2.临时保存队头元素的空间,以备释放
3.修改队头指针,指向下一个结点
4.判断出队元素是否为最后一个元素,若是,则将队尾元素指针重新赋值,指向头结点
5.释放原队头元素的空间
//链队列出队
Status DeQueue(LinkQueue &Q. QElemType &e){
if(Q.front == Q.rear) return ERROR; //若队列空,则返回ERROR
p = Q.front ->next; //p指向队头元素
e = p->data; //e保存队头元素的值
Q.front -> next = p -> next; //修改头指针
if(Q.rear == p) Q.rear=Q.front; //最后一个元素被删,队尾指针指向头结点
delete p; //释放原队头元素的空间
return OK;
}
需要注意的是,在链队出队操作时还要考虑当队列中最后一个元素被删后,队列尾指针也丢失了,因此需对队尾指针重新赋值(指向头结点)。
- 取队头元素
//链队列的队头元素
Status GetHead(LinkQueue Q,QElemType &e){
if(Q.front == Q.rear) return ERROR; //队列非空
e = Q.front -> next -> data ; //返回队头元素的值,队头指针不变
return OK;
}
- 队列销毁
//销毁链队列
Status DestroyQueue(LinkQueue &Q){
while(Q.front){ //队头还存在数据
p = Q.front->next;
free(Q->front);
Q.front = p;
}//Q.rear = Q.front->next; free(Q.front); Q.front = Q.rear;
return OK;
}
声明:此博客只是作者为了熟悉知识点而写的笔记
借鉴:《数据结构》(C语言版)(第2版)严蔚敏