队列,循环队列,链队列的基础操作

队列

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(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]; //返回对头指针元素的值,队头指针不变 
} 

由以上可知,如果用户的应用程序中设有循环队列,则必须为它设定一个最大队列长度;若用户无法预估所用队列的最大长度,则宜采用链队。

扫描二维码关注公众号,回复: 8970247 查看本文章

链队列

链队列:在链表的基础上实现的队列结构。
链式队列的实现思想同顺序队列类似,只需创建两个指针(命名为 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版)严蔚敏

发布了12 篇原创文章 · 获赞 58 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/diviner_s/article/details/104151115