特殊的线性表:队列的基本操作(循环队、链队)

版权声明:转载请注明出处~~~ https://blog.csdn.net/lesileqin/article/details/88410689

声明:博文所有gif图均为本人原创……

队列(queue)

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出(First In First Out)的线性表 ,简称FIFO。允许插入的一段叫做队尾,允许删除的一段称为队头

队列的抽象数据类型

ADT 队列(Queue)
{
	Data
		同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
	Operation
		InitQueue(&Q)	队列初始化
		DestroyQueue(&Q)	若队列存在,销毁队列
		ClearQueue(&Q)	将队列清空
		QueueEmpty(Q)	若队列为空返回true,否则返回false
		GetHead(Q,&e)	若队列存在,则用e返回队头
		EnQueue(&Q,e)	入队,插入新元素e到队尾
		DeQueue(&Q,*e)	出队,用e返回其值
		QueueLength(Q)	返回队列的个数		
}ADT Queue

队列也是一种线性表,也有顺序存储和链式存储两种存储形式。我们先看队列的顺序存储模式。

循环队列

队列顺序存储形式的不足:

我们假设一个队列有n个元素,则顺序存储的队列至少要建立一个大于n的数组,并把数据存入数组的前n个单元,队头为下标为0的元素。我们在对队列进行插入操作的时候,其实就是在队尾追加一个元素,不需要移动任何元素,所以他的时间复杂度为O(1);但是在进行删除操作的时候,要移除第一个元素,很显然,移除完之后需要把后面的元素全都向前移一位,需要浪费很多的时间,时间复杂度为O(n)。如图所示:

入队操作

上图为入队操作,错落有致,把每个元素都插入倒最后一位。

出队操作

上图为出队操作,看的出来,每次出队后续元素都要向前移一位,时间复杂度极高。

所以,我们为了避免重复移动数据元素,如果我们去除队列元素必须存储在前n个单元的条件,出队的性能就会大大的增加,换句话说,队头不一定在下标为0的位置。我们还是拿图说话吧!

循环队列的入队

 上图为循环队列的入队操作,我们可见插入一个元素到队尾,尾指针rear就会向后移动一位,front不变

循环队列的出队

上图为循环队列的出队操作, 出队时头指针front后移,rear不变;当后面的空间插入满时,可以插在存储单元的前面。

由上图可见,我们少用一个元素空间,即队列空间大小为m时,有m-1个元素就认为是队满。所以,当头、尾指针的值相同时,则认为队空;而当尾指针在循环意义上加1后时等于头指针,则认为队满。因此,在循环队列中有:

  • 队空的条件:Q.front == Q.rear
  • 队满的条件:(Q.rear+1) % MAXSIZE == Q.front

循环队列的顺序存储结构

#define MAXQSIZE 100
typedef struct {
	QElemType *base;	//存储空间的基地址
	int front;		//头指针 
	int rear; 		//尾指针 
}SqQueue;

循环队列的初始化

【算法步骤】

  • 为队列分配一个最大容量为MAXQSIZE的数组空间,若分配失败返回OVERFLOW
  • 头指针尾指针置为0,表示队列为空
Status InitQueue(SqQueue &Q)
{
	Q.base=new QElemType[MAXQSIZE];		//为队列分配一个最大容量为MAXSIZE的数组空间
	if(!Q.base)
		return OVERFLOW;
	Q.front=Q.rear=0;		//头指针和尾指针置为0,队列为空 
        return true;
} 

求循环队列的长度

【算法步骤】

  • 直接返回(Q.rear-Q.front+MAXQSIZE)% MAXQSIZE
int QueueLength(SqQueue Q)
{
	return (Q.rear-Q.front+MAXQSIZE)%MAXQSIZE;
}

循环队列的入队

【算法步骤】

  • 判断队列是否已满,若满则返回OVERFLOW
  • 将新元素插入到队尾
  • 队尾指针加一
Status EnQueue(SqQueue &Q,QElemType e)
{
	if((Q.rear+1)%MAXQSIZE==Q.front)
		return false;
	Q.base[Q.rear]=e;
	Q.rear=(Q.rear+1)%MAXQSIZE;		//队尾指针加一
	return true; 
}

循环队列的出队

【算法步骤】

  • 判断队列是否为空,若为空,返回false
  • 保存队头元素
  • 队头指针加一
Status DeQueue(SqQueue &Q,QElemType &e)
{
	if(Q.front==Q.rear)		//队空 
		return false;
	e=Q.base[Q.front];
	Q.front=(Q.front+1)%MAXQSIZE;
	return true;
}

取队头元素

【算法步骤】

  • 判断队列是否为空,然后直接返回队头
SElemType GetHead(SqQueue Q)
{
	if(Q.front!=Q.rear)
		return Q.base[Q.front];
}

若未知最大队列长度,则宜用链队

链队

链队是采用链式存储结构来存储的,通常用单链表来表示。一个链队显然需要队头和队尾的指针才能唯一确定。为了方便起见,给链队添加一个头结点,并令头指针始终指向头结点

链队的链式存储结构

typedef struct QNode{
	QElemType data;
	struct QNode *next;
}QNode,*QueuePtr;
typedef struct{
	QueuePtr front;		//tou
	QueuePtr rear;		//wei
}LinkQueue;

链队的初始化

【算法步骤】

  • 生成新结点作为头结点,队头和队尾指针指向此结点
  • 头结点指针域置空
Status InitQueue(LinkQueue &Q)
{
	//生成头结点,队头和队尾指向此结点 
	Q.front=Q.rear=new QNode;		
	Q.front->next=NULL;		//头结点的指针域置空 
	return true;
}

链队的入队

【算法步骤】

  • 为入队元素分配结点空间,用指针p指向
  • 将新结点的数据域置为e
  • 将新结点插入到队尾
  • 修改队尾指针为p
Status EnQueue(LinkQueue &Q,QElemType e)
{
	QNode *p=new QNode;
	p->data=e;
	p->next=NULL;		 
	Q.rear->next=p;		//将新结点插入到队尾 
	Q.rear=p;		//修改队尾指针 
	return true;
}

链队的出队

【算法步骤】

  • 判断队列是否为空,若空返回false
  • 临时保存队头的元素空间,以备释放
  • 修改队头指针,指向下一个结点
  • 判断出队元素是否为最后一个元素,若是,则将队尾指针重新赋值,指向头结点
  • 释放原队头元素空间
Status DeQueue(LinkQueue &Q,QElemType &e)
{
	if(Q.front==Q.rear)
		return false;
	QNode p=Q.front->next;		//指向队头
	e=p->data;
	Q.front->next=p->next;		//修改头指针
	if(Q.rear==p)
		Q.rear=Q.front;		//最后一个元素被删,队尾指针指向头结点
	delete p;
	return true; 
}

取队头元素

【算法步骤】

  • 若队不为空,返回当前首元结点的数据域
QElemType GetHead(LinkQueue Q)
{
	if(Q.front!=Q.rear)
		return Q.front->next->data;		
}

栈的基本操作请访问:https://blog.csdn.net/lesileqin/article/details/88381142


博客内容借鉴于:

①《数据结构》  作者:严蔚敏

②《大话数据结构》 作者:程杰

猜你喜欢

转载自blog.csdn.net/lesileqin/article/details/88410689