数据结构学习之四——栈与队列(二)

队列

队列的定义

队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的线性表(First In First Out),其允许插入的一端是队尾,允许删除的一端为队头。【就像是日常生活中的排队一样,从队尾入队,从队头出队】

循环队列

队列身为一种特殊的线性表,也存在着顺序存储结构和链式存储结构,但是进行分析就会知道,如果按照一般线性表来对队列进行顺序存储结构的操作,时间复杂度极大。
队列的特性是先进先出,那么插入是直接从尾部插入,其时间复杂度为O(1),但是删除的话,必然删除的是队头,即数组中下标为0的内存空间,之后就需要将数组空间中所有的元素都向前移位,以保证数组下标为0的空间中存储队头的数据元素,那么可以得到这个操作的时间复杂度为O(n),显然不能这样。

为了将时间复杂度降低,我们可以将队头指针设定为可移动的,就像是队尾每次插入都会后移一样,队头每次删除,队头指针就要进行后移。
但是这样的话队头前的数组空间就空闲下来了,如果队尾指针到了数组的尾部,按照原本的思想,这个队列就无法再插入了(又称为假溢出),但是队头前的空闲空间没有使用,为此,人们设计了一种新的队列来解决这种方法。

循环队列定义

为了解决假溢出的问题,我们将队列的顺序存储结构设定为头尾相接的循环,这种结构称为循环队列。【这里的头尾相接是指在队尾指针达到数组的尾部时,再次插入就将队尾指针移动到数组的起始

但是这种情况下,队列空可以用front == rear来表示(front队头指针,rear队尾指针),队列满也能用它来表示,此时就出现了分歧。为了避免这种分歧,我们规定:循环队列中要留出一个空间出来,不进行存储,只用来辅助判断队列是否已满。
此时队满的条件为:(rear+1)%QueueSize==front

当rear>front的时候,队列的长度为rear-front(此时rear在front的右方),当rear<front的时候,队列的长度为rear+QueueSize-front(rear在front的左方,所以加上一个QueueSize来补足)。
由此可知通用的队列长度计算公式为:(rear+QueueSize-front)%QueueSize

循环队列的顺序结构代码:

typedef int QElemType;
typedef struct
{
	QElemType data[MAXSIZE];
	int front;
	int rear;
}SqQueue;
循环队列的操作

循环队列入队代码:

Status EnQueue(SqQueue *Q,QElemType)
{
	if((Q->rear+1)%MAXSIZE==Q->front) /*队满*/
		return false;
	Q->data[Q->rear]=e;
	Q->rear=(Q->rear+1)%MAXSIZE;
	return true;
}

循环队列出队代码:

Status DeQueue(SqQueue *Q,QElemType)
{
	if(Q->rear==Q->front)/*队空*/
		return false;
	*e=Q->data[Q->front];
	Q->front=(Q->front+1)%MAXSIZE;
	return true;
}
队列的链式存储结构

队列的链式存储结构和线性表的单链表相差不大,仅仅是队列只能尾进头出,以链式结构存储。队列的链式存储结构被称为链队列
队头指针front指向头结点(不存储数据),队尾指针rear指向的结点为尾结点(存储数据)。当队列为空时,front和rear都指向头结点。

链队列结构代码:

typedef int QElemType;
typedef struct QNode
{
	QElemType data;
	struct QNode *next;
}QNode,*QueuePtr;
typedef struct
{
	QueuePtr front,rear;
}LinkQueue;
链队列的入队操作

队列的入队操作仅在队尾进行。其代码如下:

Stauts EnQueue(LinkQueue *Q,QElemType e)
{
	QueuePtr s=(QueuePtr)malloc(sizeof(QNode));
	if(!s)/*空间开辟失败*/
		exit(OVERFLOW);
	s->data=e;
	s->next=NULL;/*给s结点赋值*/
	Q->rear->next=s;/*将s结点接在链队列的队尾*/
	Q->rear=s;/*让s成为链队列的队尾*/
	return true;
}
链队列的出队操作

出队操作的核心就是将头结点的后继结点出队,头结点的后继改为后面那个结点。先进行头结点后继的赋值,在进行出队结点的删除。
其代码如下:

Status DeQueue(LinkQueue *Q,QElemType *e)
{
	QueuePtr p;
	if(Q->front==Q->rear) /*链队列为空*/
		return false;
	p=Q->front->next;
	*e=p->data;
	Q->front->next=p->next;
	if(Q->rear==p)/*队头为队尾的话,删除后队空,要将rear指向头结点*/
		Q->rear=Q->front;
	free(p);
	return true;
}
循环队列和链队列的比较分析

不论是循环队列还是链队列,其基本操作的时间复杂度均为O(1)。
从时间上来说循环队列事先申请好了空间,不需要再进行空间的申请和释放,链队列申请和释放空间需要一定的时间开销,如果入队出队很频繁的话链队列并不是很好的选择。而在空间上来说,链队列有着固定的长度,很可能有空间浪费和存储空间不足的问题,链队列则不存在这种问题,链队列更加灵活。
所以,在可以确定队列的最大长度的情况下,建议使用循环队列,如果无法预估队列的长度,就使用链队列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值