目录
1. 队列的定义
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
2. 队列的抽象数据类型
同样是线性表,队列也有类似线性表的各种操作,不同的就是插入数据只能在队尾进行,删除数据只能在队头进行。
3. 循环队列
线性表有顺序存储和链式存储,栈是线性表,所以有这两种存储方式。同样,队列作为一种特殊的线性表,也同样存在这两种存储方式。
队列顺序存储的不足
若要保证下标为0的位置不为空,则出队需要将后面的元素全都往前移动,时间复杂度为O(n);
若采用双指针标志队头元素和队尾元素,不要求队头元素一定在下标为0的位置,则可能会发生“假溢出”。
循环队列定义
解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。
继续入队:
此时问题:空队列时,front 等于 rear,现在当队列满时,也是front等于rear,那么如何判断此时的队列究竟是空还是满呢?
办法一:设置一个标志变量flag,当front == rear,且flag=0时为队列空,当front==rear,且flag=1时为队列满。
办法二:当队列空时,条件就是front=rear,当队列满时,我们修改其条件,保留一个元素空间。也就是说,队列满时,数组中还有一个空闲单元。
对于第二种方法:
假设队列最大尺寸为QueueSize,那么队列满的条件是(rear+1)%QueueSize == front(取模"%"的目的就是为了整合 rear 与 front 大小为一个问题)。
当rear>front时,此时队列的长度为rear-front;
但当rear<front时,队列长度分为两段,一段是QueueSize-front,另一段是0+rear,加在一起,队列长度为rear-front+QueueSize;
因此通用的计算队列长度公式为:(rear-front + QueueSize)%QueueSize
4. 实现循环队列的代码
循环队列的顺序存储结构
循环队列的初始化
循环队列求队列长度
循环队列的入队列操作
循环队列的出队列操作
单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题。
5. 队列的链式存储及实现
队列的链式存储结构,其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
为了操作上的方便,我们将队头指针指向链队列的头结点,而队尾指针指向终端结点,空队列时,front和rear都指向头结点。
链队列的结构
链队列的入队操作
链队列的出队操作
对于循环队列与链队列的比较,可以从两方面来考虑,从时间上,其实它们的基本操作都是常数时间,即都为O(1) 的,不过循环队列是事先申请好空间,使用期间不释放,而对于链队列,每次申请和释放结点也会存在一些时间开销,如果入队出队频繁,则两者还是有细微差异。对于空间上来说,循环队列必须有一个固定的长度,所以就有了存储元素个数和空间浪费的问题。而链队列不存在这个问题,尽管它需要一个指针域,会产生一些空间上的开销,但也可以接受。所以在空间上,链队列更加灵活。
总的来说,在可以确定队列长度最大值的情况下,建议用循环队列,如果你无法预估队列的长度时,则用链队列。
6. 总结
栈和队列都是特殊的线性表,只不过对插入和删除操作做了限制。
栈(stack)是限定仅在表尾进行插入和删除操作的线性表。
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。