昨天写了一篇关于数据结构栈的知识,今天有空就整理了一下队列。像栈一样,队列(queue)也是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。把两者之间对比来看,可以发现各有优缺点吧!!!
一、队列的定义
1、队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。队列有顺序队列和链队列,顺序队列在这里分成循环队列。
2、队列是一种先进先出的线性表,简称FIFO
。允许插入的一端为队尾,允许删除的一端称为对头。如队列q=(a1,a2,.......,an)
的示意图如下:
二、循环队列
1、队列顺序存储:假设队列有n个元素,则顺序存储的队列需要建立一个大于n的数组,并把队列的所有元素存储在数组的前n个单元,数组下标为0的一端即是对头;然后入队操作就是在队尾加一个元素,不需要移动任何元素,所以时间复杂度是O(1);但是出队是在对头,即下标为0的位置,所以后面的所有元素得向前移动,以保证下标为0的位置不为空,时间复杂度为O(n)。
2、在1的情况下,如果不限制队列的元素必须存储在数组的前n个单元这一条件,则出队的性能就会大大增加,即对头不需要一定在下标为0的位置。如下图:
3、在2的情况下为了避免当只有一个元素时,对头和队尾重合使处理变麻烦,所以用front指针指向对头元素,rear指针指向队尾元素的下一个位置。当front
等于rear
时,就是空队列。如下图 :
4、如下图所示,当入队a5时,rear
指针都移动到数组之外去了,且如果接着入队的话,就会产生数组越界的错误,可实际上队列在下标为0和1的地方还是空闲的,把该种现象叫做“假溢出”。
5、循环队列定义:解决假溢出的办法就是后面满了,就再从头开始,也就是头尾想接的循环,把队列的这种头尾想接的顺序存储结构称为循环队列。
6、采用循环队列,如下图,此时font
等于rear
,是队列满时,但是在没采用循环队列的3的情况下,front
等于rear
是空队列的情况,此时采取的办法是当front=rear
时依然是定义的空队列,而当数组中还有一个空闲空间时就定义是队列满时。如下图左边部分就认为是队列满,而不允许出现下图右边部分的情况。
7、采用6中定义队列满时,上图左边部分是一种满时情况,此时rear
比front
小,它们相差1,如下图也是一种满时情况,此时rear
比front
大,它们相差整整一圈。所以如果队列的最大尺寸为QueueSize
,那么队列满的条件是(rear + 1) % QueueSize = front
。
8、采用6中定义队列满时,如上图当rear
大于front
,此时队列的长度计算方式是:rear-front
。如6中的图的左边部分所示,当rear
小于front
,此时队列长度分为两段,分别是QueueSize-fron
t和0+rear
,即rear-front+QueueSize
,当图中rear
不是指向1,而是指向0,这里的两段的计算方式也适合,所以通用的计算队列长度公式为:(rear - front + QueueSize) % QueueSize
。
9、循环队列的顺序存储结构代码如下:
1 | #define OK 1 |
10、循环队列的初始化代码:
1 | /* 初始化一个空队列Q */ |
11、循环队列求队列长度代码:
1 | /* 返回Q的元素个数,也就是队列的当前长度 */ |
12、循环队列的入队列操作:
1 | /* 若队列未满,则插入元素e为Q新的队尾元素 */ |
13、循环队列的出队列操作:
1 | /* 若队列不空,则删除Q中队头元素,用e返回其值 */ |
三、队列的链式存储结构及实现
1、将队列的链式存储结构简称为链队列,为了操作方便,将对头指针指向链队列的头结点,而队尾指针指向终端结点,当front
和rear
都指向头结点时就是空队列,如下图:
2、链队列的结构代码:
1 | #define OK 1 |
3、入队操作:插入示意图如下:
实现代码:
1 | /* 插入元素e为Q的新的队尾元素 */ |
4、出队操作:出队操作时就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,需要将rear
指向头结点,如下图:
实现代码:
1 | /* 若队列不空,删除Q的队头元素,用e返回其值,并返回OK,否则返回ERROR */ |
四、总结回顾
在该篇博客中,我们学习了队列。结合上一篇知道了两者都是特殊的线性表,相当于对线性表也进行了一遍复习。接下来就总结一下两者的区别:
栈(Stack)是仅限在线性表表尾进行插入和删除操作的线性表。
队列(Queue)是只允许在一端进行插入操作,另一端进行删除操作的线性表。
它们都可以使用顺序存储结构来实现,但是会有一些弊端,不过也可以通过各自的技巧来解决,像两栈共享空间、循环队列。
当然它们也可以使用链式存储结构来实现。至于哪种实现更好,就取决于在实际使用中的场景了。