队列简单介绍
队列是一种常用的数据结构之一,与之前的栈类似,不过队列是“先进先出”。队列有队头(front)和队尾(rear),数据从队尾进入队列,从队头出队列,队头(front)指向队列的第一个数据,队尾(rear)指向队列中的最后一个数据。
队列实现
队列有很多种,这里只是介绍最基本的实现,采用链式存储,也就是链式队列,与之前的链表存储形式一样,通过结点对象描述一个数据,结点对象包含具体数据和下一个结点的引用。
队列有两个重要的概念,一个叫队头,一个叫队尾,队头指向的是第一个元素,而队尾指向的是最后一个元素。队列跟栈一样也是访问受限制的,所以队列也只有两个主要的操作:入队(enqueue)操作 和 出队(dequeue)操作 。入队操作就是将一个元素添加到队尾,出队操作就是从队头取出一个元素。
队列的底层实现可以用数组和链表,基于数组实现的队列叫作顺序队列,基于链表实现的队列叫作链式队列,下面我们分别用数组和链表来简单的实现这两种队列。
基于数组的队列
不管使用那种方式来实现队列,都需要定义两个指针分别指向队头和队尾,本文中我们用head指向队头,tail指向队尾,后面的示例中这将默认使用这个,有特殊的地方我会进行说明,先来看看顺序队列的入队、出队操作。
图中可以看出,入队时,队尾往后移动,队头保持不变,出队是队头往后移动,队尾保持不变。入队、出队操作的逻辑都比较简单,可能你有疑问的地方是:出队时为什么队头要往后移动而不是一直指向数组下标为0的位置? 为什么呢?如果我们保持队头一直指向数组下标为0的位置,那每次出队操作后,后面的数据都需要往前挪一位,换句话说每次出队操作都需要进行数据迁移,而数据迁移的代价比较大,每次数据迁移的时间复杂度为O(n),这样会极大的影响队列的使用性能。如果我们出队时,队头往后移动一位,这样我们就避免每次出队都进行数据迁移,我们只需要在只有在tail等于数组大小且head不等于0时,进行一次数据迁移,将已经出队留下的空间继续供入队时使用。下图是数据迁移的过程:
数据迁移时,从head位置开始的数据都需要往前移动head位,这样就把出队后的空间腾出来,供后续入队操作使用。
基于数组的队列实现代码:
1 | /** |
链式队列
链式队列实现起来相对顺序队列来说要简单很多,我们先来看看链式队列的入队、出队操作:
从图中可以看出链式队列入队操作是将tail的next指向新增的节点,然后将tail指向新增的节点,出队操作时,将head节点指向head.next节点。链式队列与顺序队列比起来不需要进行数据的迁移,但是链式队列增加了存储成本。
基于链表的队列实现代码
1 | /** |
循环队列
循环队列是对顺序队列的改进,因为顺序队列不可避免的数据迁移操作,数据迁移操作会导致队列的性能下降,为了避免这个问题,将队列改造成循环的,当tail到达数组的最大下标时,重新指回数组下标为0的位置,这样就避免了数据迁移。先来看看循环队列的出队、入队操作:
因为队列是循环队列,所以在进行入队、出队操作时,就不能像顺序队列那样对tail、head进行简单的加1操作,我们需要对tail、head加1后与数组的大小进行求余操作,来得出tail、head的值,这样才能进行循环操作。循环队列需要牺牲一个存储空间,对于一个存储空间为n的循环队列来说只能存放n-1为数据,因为如果不牺牲一个存储空间的话,当tail==head时,就有可能存在队空或者队满的情况。
循环队列的实现代码
1 | /** |
双端队列
双端队列是一种队头、队尾都可以进行入队、出队操作的队列,双端队列采用双向链表来实现,先来看一下双端队列的入队、出队操作:
可以从动态图中看出,双端队列的每一端都是一个栈,都符合栈先进后出的特性,如果我们对双端队列进行禁止队头入队和队尾出队操作的限制,双端队列又变成了一个链式队列,双端队列是一种多功能的数据结构,我们可以使用它来提供队列和栈两种功能。
双端队列的实现代码
1 | /** |
优先队列
优先队列为一种不必遵循队列先进先出(FIFO)特性的特殊队列,优先队列跟普通队列一样都只有一个队头和一个队尾并且也是从队头出队,队尾入队,不过在优先队列中,每次入队时,都会按照入队数据项的关键值进行排序(从大到小、从小到大),这样保证了关键字最小的或者最大的项始终在队头,出队的时候优先级最高的就最先出队,这个就像我们医院就医一样,急救的病人要比普通的病人先就诊。一起来看看优先队列的出队、入队操作:
在示例中,我们规定数值越小优先级越高。我们每执行一次入队操作时,小的元素都会靠近头队,在出队的时候,元素小的也就先出队。
优先队列的代码实现
这里使用的数组实现优先队列,用数组实现主要原因是更好理解优先队列的思想。一般都是使用堆来实现优先队列,因为数组实现在插入的时候对数据的排序代价比较大。
1 | /** |
总结
队列是一种遵循先进先出(FIFO)的数据结构
队列可以使用数组和链表实现,数组实现叫作顺序队列,链表实现叫作链- 式队列
循环队列解决了顺序队列的数据迁移带来的性能损耗的问题
双端队列是队头和队尾都可以进行入队、出队操作的队列
优先队列是一种不必遵循先进先出规则的队列,任意元素加入时,都会讲- 优先级最高的放入到队头