跳转至

队列

一、结构特性

基本定义:

  • 队列是一种遵循先入先出规则的线性数据结构

主要特点:

  • 数据是先进先出,后进后出
  • 只允许从队首入队,从队尾出队

队列的两端分别称为「队首」和「队尾」。

  • 队首:允许删除的一端
  • 队尾:允许插入的一端
                   队首        队尾
                    |           |
                    v           v
                  ————————————————————————
 出队方向 <--      | 1 | 2 | 3 | 4               <-- 入队方向
                  ————————————————————————

队列类似于管道一样,从一端进入,从另一端出来。

二、常用操作

队列的常用操作有 3 种:

  • 访问:访问队首、队尾
  • 入队:从队尾入队
  • 出队:从队首出队

2.1 访问操作

队列可以访问队首和队尾,但是不能访问中间的元素。

  队首        队尾
   |           |
   v           v
 ————————————————————————
 | 1 | 2 | 3 | 4
 ————————————————————————
   ^           ^
   |           |
  访问        访问

2.2 入队操作

入队是从队尾入队,从队尾元素后追加,新添加的元素变成队尾。

  队首        队尾
   |           |
   v           v
 ————————————————————————
 | 1 | 2 | 3 | 4
 ————————————————————————

          | 入队 5
          v

  队首            队尾
   |               |
   v               v
 ————————————————————————
 | 1 | 2 | 3 | 4 | 5
 ————————————————————————

2.3 出队操作

出队是从队首出队,将队首元素删除,队列的第二个元素变成队首。

  队首        队尾
   |           |
   v           v
 ————————————————————————
 | 1 | 2 | 3 | 4
 ————————————————————————

        | 出队 1
        v

  队首    队尾
   |       |
   v       v
 ————————————————————————
 | 2 | 3 | 4
 ————————————————————————

三、实现方式

根据队列的实现方式,可以分为几种:

  • 顺序队列:基于数组实现
  • 链式队列:基于链表实现
  • 循环队列:队列的头尾相连,形成一个环

3.1 顺序队列

顺序队列是基于数组实现的队列。

  队首        队尾
   |           |
   v           v
  ___ ___ ___ ___ ___ ___ ___
 | 1 | 2 | 3 | 4 |   |   |   |      <- 数组

入队一般是在数组末端:

  队首            队尾
   |               |
   v               v
  ___ ___ ___ ___ ___ ___ ___
 | 1 | 2 | 3 | 4 | 5 |   |   |

入队操作时间复杂度是 O(1)。

出队一般是在数组首端:

      队首    队尾
       |       |
       v       v
  ___ ___ ___ ___ ___ ___ ___
 |   | 2 | 3 | 4 |   |   |   |

出队操作时间复杂度是 O(1)。

这里有一个问题,就是队尾会一直往后移,当抵达数组末端时,就没办法再插入了。

遇到这种情况,一般需要进行迁移,将队列元素移到首端这边:

              队首        队尾
               |           |
               v           v
  ___ ___ ___ ___ ___ ___ ___
 |   |   |   | 4 | 5 | 6 | 7 |

               | 迁移
               v 

  队首        队尾
   |           |
   v           v
  ___ ___ ___ ___ ___ ___ ___
 | 4 | 5 | 6 | 7 |   |   |   |

然后就能继续入队了,不过这会对性能有一些影响。

3.2 链式队列

链式队列是基于链表实现的队列。

  队首                                队尾
   |                                   |
   v                                   v
  ___         ___         ___         ___
 | 1 |  -->  | 2 |  -->  | 3 |  -->  | 4 |  -->  NULL

入队一般是在链尾:

  队首                                            队尾
   |                                               |
   v                                               v
  ___         ___         ___         ___         ___
 | 1 |  -->  | 2 |  -->  | 3 |  -->  | 4 |  -->  | 5 |  -->  NULL

入队操作时间复杂度是 O(1)。

出队一般是在链首:

  队首                    队尾
   |                       |
   v                       v
  ___         ___         ___ 
 | 2 |  -->  | 3 |  -->  | 4 |  -->  NULL

出队操作时间复杂度是 O(1)。

链式队列就不需要迁移了,因为链表没有大小限制。

3.3 循环队列

循环队列是将顺序队列的头尾相连,形成一个环形。

循环队列是对顺序队列的一种优化,用于避免迁移操作。

比如说,当循环队列的数据抵达数组末端时:

              队首        队尾
               |           |
               v           v
  ___ ___ ___ ___ ___ ___ ___
 |   |   |   | 4 | 5 | 6 | 7 |      <- 数组

此时,再下一个入队的位置,就是在数组的首端:

  队尾        队首
   |           |
   v           v
  ___ ___ ___ ___ ___ ___ ___
 | 8 |   |   | 4 | 5 | 6 | 7 |      <- 数组

这样就形成了一个环,从而避免了数组元素的迁移。

循环队列在实际中的使用会更多一些。

四、复杂度

顺序队列:

  • 入队:时间复杂度 O(1),空间复杂度 O(1)
  • 出队:时间复杂度 O(1),空间复杂度 O(1)

链式队列:

  • 入队:时间复杂度 O(1),空间复杂度 O(1)
  • 出队:时间复杂度 O(1),空间复杂度 O(1)

循环队列:

  • 入队:时间复杂度 O(1),空间复杂度 O(1)
  • 出队:时间复杂度 O(1),空间复杂度 O(1)

五、典型应用

  • 连接池的等待队列
  • 没有空闲资源时,使用队列排队等待
  • 淘宝订单
  • 各类待办事项,需要先进先出的,比如排队
  • 缓冲区管理(循环队列)
  • 数据传输(循环队列)
  • 轮询调度(循环队列)

参考

https://www.hello-algo.com/chapter_stack_and_queue/queue/

https://time.geekbang.org/column/article/41330