首页 > 代码库 > 09.循环队列与链队列
09.循环队列与链队列
一、队列与循环队列
1.队列
(1)队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列是一种先进先出(Fiirst In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
从队列的定义可知,队列的入队操作,其实就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度为O(1)。队列的删除操作,与栈不同的是,队列元素的出列是在队头,即小标为0的位置,若要删除一个元素的话,需要移动队列的所有元素,因此事件复杂度为O(n)。
(2)front/rear指针:为了避免当只有一个元素时,队尾和队头重合使处理变得麻烦,所有引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear时,队列为空(空队列)而不是队列还剩下一个元素。
ADT 队列
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitQueue(*Q): 初始化操作,建立一个空队列Q.
DestoryQueue(*Q):若队列Q存在,则销毁它。
ClearQueue(*Q):将队列Q清空
GetHead(Q,*e):若队列Q存在且非空,用e返回队列Q的队头元素。
EnQueue(*Q,e):若队列Q存在且非空,插入新元素e到队列Q中并称为队尾元素。
DeQueue(*Q,*e):删除队列Q中队头元素,并用e返回其值
QueueLength(Q):返回队列Q的元素个数
endADT
3.循环队列 (1)定义:队列中头尾相接的顺序存储结构称为循环队列,用于解决"假溢出"问题。
(2)队满条件:
一般情况,当front=rear时,队列可能为空队列也可以为满队列。所以我们假设,当front==rear时队列为空;当队列满时,数组还有一个空闲空间。由于rear可能比front大,也可以比front小。我们这样定义,当队列满足条件"(rear+1)%QueueSize==front"时,我们就认为队列已满(QueueSize为队列最大存储容量)。
(3)队列长度公式
当rear>front时,队列的长度为rear-front;当rear<front时,队列的长度为(QueueSize-front)+(0+rear),其中font、rear、QueueSize均为数组下标。经过推导的计算队列公式:
(rear-front+QueueSize)%QueueSize
(4)循环队列的顺序存储结构
typedef int QElemType
typedef struct
{
QElemType data[MAXSIZE];
int front; //队头指针
int rear; //队尾指针,若队列不空,指向队尾元素的下一个位置
}SqQueue;
(5)循环队列的相关操作
A.初始化一个循环队列Q
Status InitQueue(SqQueue *Q)
{
Q->front=0;
Q->rear=0;
return OK;
}
B.计算循环队列的长度
/*返回Q的元素个数,即队列的当前长度*/
int QueueLength(SqQueue Q)
{
return (Q.rear-Q.front+MAXSIZE)%MAXSIZE;
}
C.循环队列插入操作
实现:若队列未满,则插入元素e为新的队尾元素
Status EnQueue(SqQueue *Q,QElemType e)
{
if((Q->rear+1)%MAXSIZE == Q->front) //判断栈满((rear+1)%QueueSize==front)
return ERROR;
Q->data[Q->rear]=e;
Q->rear=(Q->rear+1)%MAXSIZE; //rear指针向后移一位,若到最后则转到数组头部
return OK;
}
D.循环队列删除操作
实现:若队列不空,则删除Q中队头元素。用e返回其值
Status EnQueue(SqQueue *Q,QElemType *e)
{
if(Q->rear==Q->front)
return ERROR; //队列为空条件:rear=front
*e=Q->data[Q->front]; //将队头元素赋值给e
Q->front=(Q->front+1)%MAXSIZE; //front指针向后移一位置,若到最后则转到数组头部
return OK;
}
总结:单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题,所以我们下节引出队列的链式存储结构。
二、队列的链式存储结构1.链队列:队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出,也称链队列。为了方便操作,队头指针指向链队列的头结点,队尾指针指向终端结点。当队头指针front和队尾指针rear都指向头结点时,队列为空。
typedef int QElemType
/*结点结构*/
typedef struct QNode
{
QElemType data; //数据域
struct QNode *next; //指针域
}QNode,*Queueptr;
/*队列的链表结构*/
typedef struct
{
Queueptr front,rear; //队头、队尾指针
}LinkQueue;
3.链队列的入队操作算法:实质为链表尾插入结点,尾指针指向新结点
a.为新结点s开辟一段空间,并判断是否存储分配成功;
b.将插入元素存到新结点s的数据域,并初始化s的指针域;
c.把拥有元素e新结点s赋值给原队列结点的后继;
d.把当前的s设置为队尾结点,rear指向s
实现:插入元素e为Q的新队尾元素
Status EnQueue(LinkQueue *Q,QElemType e) { QueuePtr s=(QueuePtr)malloc(sizeof(QNode)); //为新结点s开辟一段空间 if(!s) //存储分配失败 exit(OVERFLOW); s->data=http://www.mamicode.com/e; //将元素e存储到新结点s的数据域>
4.链队列的删除操作
算法:实质是头结点的后继结点出队,将头结点的后继改为他后面的结点。若链表除头结点外,只剩下一个元素时,则需将rear指向头结点。
a.定义一个QueuePtr结点p,用于暂存欲删除的结点Q->front-next;
b.将欲删除结点数据域数据赋值给e;
c.将欲删除结点的后继结点(p->next)赋值给头队头结点的后继(Q->front->next)
d.判定若队头是队尾,则删除后将rear指向头结点,最后再释放欲删除结点p.
实现:若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR
Status EnQueue(LinkQueue *Q,QElemType e) { QueuePtr p; if(Q->front==Q->rear) return ERROR; //队列为空 p=Q->front->next; //将队头指针指向的头结点的后继结点(欲删除的结点)暂存给p *e=p->data; //将欲删除结点的数据域数据赋值给e Q->front->next=p->next; //将元队头结点后继p->next赋值给队头结点的后继 if(Q->rear==p) //若队头是队尾,则删除后将rear指向头结点 Q-rear=Q->front; free(p); return OK; }
1.循环队列与链队列基本操作时间复杂度均为O(1);
2.循环队列事先申请好空间,使用期间不释放;链队列,无需事先申请空间但是每次申请和释放结点会存在一些事件开销;
3.循环队列必须有一个固定的长度,可能存在空间浪费;链队列需要一个指针域,会产生一些空间上的开销,所以在可以确定队列长度最大值的情况下建议用循环队列。
09.循环队列与链队列
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。