2.07——队列
1. 什么是队列
队列(queue)是只允许在一端进行插入操作,而在另一端进行数据操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端成为队尾,允许删除的一端成为队头。假设队列是q=(a1, a2, a3, …, an),那么a1就是队头元素,而an是队尾元素。这样我们就可以删除时,再试从a1开始,而插入时,列在最后。
2. 队列的存储结构
线性表有循序存储和链式存储,栈是线性表,所以有这两种存储方式。同样,队列作为一种特殊的线性表,也同样存在着两种存储方式。顺序队列用数组存储,链式队列有链表存储。
3. 队列的顺序存储结构
假设一个队列有n个元素,则顺序存储的队列需建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是队头。所谓的入队列操作,其实就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度为O(1)。与栈不同的是,队列元素的出列是在队头,即下标为0的位置,那也就意味着,队列中的所有元素都得向前移动,以保证队列的队头,也就是下标为0的位置不为空,此时时间复杂度为O(n)。
4.什么是队列的假溢出
为了避免当只有一个元素时,对头和队尾重合使初始变得麻烦,所以引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位,这样当front等于rear时,此队列不是还剩一个元素,二十空队列。
假设是长度为5的数组,初始状态,空队列。front与rear指针均指向下标为0的位置。然后入队a1、a2、a3、a4,front指针移入指向下标为0位置,而rear指针指向下标为4的位置。
出队a1、a2,则front指针指向下标为2的位置,rear不变,在入队a5,此时front指针不变,rear指针移动到数组之外。
如果这个队列的总个数不超过5个,但目前如果接着入队的话,因数组末尾元素已经占用,再向后加,就会产生数组越界的错误,可实际上,我们的队列在下标为0和1的地方还是空闲的。我们把这种现象叫做“假溢出”。
5. 循环队列的定义
为了解决假溢出的办法就是后面满了,就从头开始,也就是头尾详解的循环。我们把队列的这种头尾相接的顺序存储结构成为循环队列。如果队列满了,rear就指向下标为0的位置,这样就不会造成指针指向不明的问题了。
6. 队列的链式存储结构
队列的链式存储结构,其实就是线性表的单链表,比不过它只能尾进头出而已,我们把它简称为链队列。为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端结点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jJKOAx1a-1590541551466)(http://p3glgyim1.bkt.clouddn.com//img/6.png)]
空队列时,front和rear都指向头结点。
typedef int QElemtype;
typedef struct QNode
{
QElemType data;
struct QNode *next;
}QNode, *QueuePtr;
typedef struct
{
QueuePtr front,rear;
}LinkQueue;
7. 链式队列的入队
入队操作时,其实就是在链表的尾部插入结点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UDf8H9DS-1590541551467)(http://p3glgyim1.bkt.clouddn.com//img/clip1517984231.png)]
其代码如下:
Status EnQueue(LinkQueue *Q, QElemType e)
{
QueuePtr s =(QueuePtr)malloc(sizeof(QNode));
if(!s)
{
eixt(OVERFLOW);
}
s->data = e;
s->next = NULL;
Q->rear-next = s;
Q-rear = s;
return OK;
}
8. 链式队列的出队操作
出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表出头结点外只剩一个元素时,则需将rear指向头结点。
代码如下:
Status DeQueue(LinkQueue *Q, QElemtype *e)
{
QueuePtr p;
if (Q->front == Q->rear)
{
return ERROR ;
}
p = Q->front->next;
*e=p->data;
Q->front->next = p->next;
if(Q->rear == p)
{
Q->rear = Q->front;
}
free(p);
return OK;
}
9. 循环队列与链队列的比较
可以从两方面来考虑,从时间上,其实它们的基本操作都是常数时间,即都是为O(1)的,不过循环队列是事先申请号空间,使用期间不是放,而对于链队列,每次申请和方式结点袁辉存在一些时间开销,如果入队和出队频繁,则两者还是有细微差异。对于空间上开说,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在这个文件,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接收。所处在空间上,见队列更加灵活。
10. 栈和队列的比较
栈(stack)是限定在表尾进行插入和删除操作的线性表。
队列(queue)是只允许在一端进行插入操作,而在另一端进行数据操作的线性表。
它们均可以用线性表的顺序存储结构来实现,但都存在着顺序存储的一些弊端。因此它们各自有各自的技巧来解决这个问题。
对于栈来说,如果是两个相同数据类型的栈,则可以用数组的两端作栈底的方法来让两个栈共享数据,这就可以最大化地利用数组的空间
对于队列来说,为了避免数组插入和删除时需要移动数据,于是就引入了循环队列,使得队头和队尾在数组中循环变化。解决了移动数据的时间损耗,使得真来插栓换删除是O(n)的时间复杂度变了了O(1)。
它们也都可以通过监视存储结构来实现,实现原则上与线性表基本相同如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1iHSknC1-1590541551469)(http://p3glgyim1.bkt.clouddn.com//img/clip1517985682.png)]