Skip to content

队列的工程应用:BFS、任务调度与生产消费

基于通用算法套路 · 核于 2026-07

速查

  • BFS(广度优先搜索):队列的灵魂应用——把 DFS 的「深度优先」换成「按层扩展」,用队列维护「待访问层」;树/图的层序遍历、最短步数(无权图)、岛屿数量都是 BFS。
  • BFS 框架:起点入队 → while 队列非空:出队处理 → 未访问邻居入队标记——保证先访问的先扩展,天然按距离分层。
  • 任务调度:操作系统进程就绪队列(FCFS 先来先服务)、打印机任务队列——FIFO 保证「先到先执行」的公平性。
  • 生产者-消费者(阻塞队列):生产者入队、消费者出队,队列满则生产者阻塞、队列空则消费者阻塞——用有界队列 + 锁/条件变量实现线程间同步与缓冲。
  • 消息队列(MQ):分布式系统的「解耦 + 削峰 + 异步」——Kafka/RabbitMQ/RocketMQ 本质是跨进程的持久化队列。
  • 优先队列(Priority Queue):出队按优先级而非到达顺序,底层是——普通队列 FIFO 不够用时升级(Dijkstra 最短路、Top-K、合并 K 个有序链表)。
  • JS 异步任务队列:事件循环维护「宏任务队列」+「微任务队列」——setTimeout 进宏队列、Promise.then 进微队列,微任务优先级更高(每轮宏任务前清空微任务)。
  • 复杂度:BFS O(V+E);阻塞队列入队出队 O(1)(加锁开销另算);优先队列入队出队 O(log n)(堆调整)。
  • 选型:要 FIFO 公平 → 普通队列;要最短路径/分层 → BFS;要优先级 → 优先队列(堆);要跨进程解耦 → 消息队列。

一、BFS:队列的灵魂应用

广度优先搜索(BFS, Breadth-First Search)用队列维护「待访问节点」,天然按距离起点从近到远的顺序扩展——这是队列 FIFO 语义的直接体现:先入队(更近)的先出队处理。

通用 BFS 框架

js
function bfs(start, graph) {
  const queue = [start];        // 起点入队
  const visited = new Set([start]); // 入队即标记,避免重复入队
  while (queue.length) {
    const node = queue.shift(); // 出队(FIFO:先入先处理)
    // 处理 node(如记录层数、判断目标)
    for (const next of graph[node] || []) {
      if (!visited.has(next)) {
        visited.add(next);      // 标记要在入队时,不是出队时
        queue.push(next);       // 邻居入队
      }
    }
  }
}

经典:二叉树层序遍历(LeetCode 102)

把「图」换成「树的左右子节点」,BFS 天然按层输出:

js
function levelOrder(root) {
  if (!root) return [];
  const res = [], queue = [root];
  while (queue.length) {
    const level = [], n = queue.length; // 关键:记录当前层节点数
    for (let i = 0; i < n; i++) {       // 恰好处理一层
      const node = queue.shift();
      level.push(node.val);
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
    res.push(level);
  }
  return res;
}

关键技巧const n = queue.length 在每轮 while 开始时记录当前层节点数,内层 for 恰好处理一整层——这是「按层」的核心。

经典:无权图最短步数

BFS 第一次到达目标时的层数就是最短步数(无权图「边数最少」)——因为 BFS 按距离分层扩展,第一次碰到的就是最近的。代表题:单词接龙(127)、腐烂的橘子(994)、迷宫最短路径。

注意:JS 的 queue.shift() 是 O(n)(整体搬移),大图 BFS 会退化成 O(n²)。工程上要用循环数组或链表自实现队列,或用 let i = 0 配合 queue[i++] 模拟出队。

二、任务调度:FIFO 公平性

操作系统、中间件大量用队列做「先到先服务」的任务调度:

  • 进程就绪队列(FCFS):就绪进程按到达顺序排队,调度器从队头取一个执行——这是最简单的调度策略(先来先服务)。
  • 打印机任务队列:打印任务按提交顺序排队,先提交的先打印——典型的 FIFO 缓冲。
  • 消息分发队列:WebSocket / 长连接的消息按到达顺序投递,保证顺序性。

队列在这里的价值是公平 + 解耦:提交方(生产者)只管入队,处理方(消费者)只管出队,两者解耦,且顺序天然保证。当公平性不够(要紧急插队)时,升级为优先队列(见后)。

三、生产者-消费者:阻塞队列

生产者-消费者模型用有界队列做生产者和消费者之间的缓冲:生产者往队列里入队数据,消费者从队列出队处理。当队列时生产者阻塞(等消费者取走),当队列时消费者阻塞(等生产者放入)——这叫阻塞队列(Blocking Queue)

生产者 ──入队──> [ 有界阻塞队列 ] ──出队──> 消费者
   (满了就阻塞等)                  (空了就阻塞等)
  • 价值削峰填谷(生产快于消费时队列缓冲,不丢数据)+ 解耦(生产消费各自速率)+ 线程同步(队列的锁/信号量天然串行化)。
  • 实现:C++ std::mutex + condition_variable;Java BlockingQueue 接口(ArrayBlockingQueue / LinkedBlockingQueue);Go 用带缓冲的 channel
  • 有界 vs 无界:有界队列防止生产过快 OOM(内存溢出),需要背压(backpressure);无界队列可能堆积爆内存。

四、消息队列:分布式解耦

消息队列(Message Queue, MQ)是生产者-消费者模型的跨进程、跨机器版本——生产者把消息发到 MQ,消费者从 MQ 订阅,中间可持久化、可重放、可削峰。

  • 三大价值:①解耦(生产者不关心谁消费);②异步(生产者发完就走,消费者慢慢处理);③削峰(突发流量先进队列,消费者按自己的速率处理)。
  • 代表产品:Kafka(高吞吐、日志流)、RabbitMQ(灵活路由)、RocketMQ(事务消息)、Redis Streams(轻量)。
  • 本质:一个持久化、可分区、可复制的「巨型队列」——把单机的阻塞队列思想放大到分布式。

五、优先队列:FIFO 不够用时

普通队列严格 FIFO,但很多场景要优先级高的先出(而非先到的先出)——这就是优先队列(Priority Queue)。它不按到达顺序出队,而按优先级出队,底层用**堆(Heap)**实现:

  • :完全二叉树,父节点恒 ≥(大顶堆)或 ≤(小顶堆)子节点;入队出队都是 O(log n)(向上/向下调整)。
  • 应用:Dijkstra 最短路(小顶堆按距离出队)、Top-K(小顶堆维护前 K 大)、合并 K 个有序链表(小顶堆每次取最小)、任务调度(紧急任务优先)。
  • 与普通队列对比:普通队列入队出队 O(1) 但无优先级;优先队列入队出队 O(log n) 但支持优先级——用 O(log n) 换取「动态最值」。

优先队列的底层(堆)是另一个叶的主题,这里只需理解它是「队列按优先级出队」的升级版,触发条件是「FIFO 公平不够,要按重要性排序」。

六、JS 异步任务队列:事件循环

JavaScript 是单线程,靠事件循环(Event Loop) + 任务队列实现异步非阻塞。它维护两类队列:

  • 宏任务队列(Macrotask Queue)setTimeout / setInterval / I/O / UI 渲染 / postMessage 等。
  • 微任务队列(Microtask Queue)Promise.then/catch/finally / queueMicrotask / MutationObserver 等。

执行规则

  1. 从宏任务队列取一个任务执行。
  2. 清空所有微任务队列(微任务可以产生新微任务,全部执行完)。
  3. 必要时渲染 UI。
  4. 回到第 1 步。
js
console.log('1');                    // 同步
setTimeout(() => console.log('2'));  // 宏任务
Promise.resolve().then(() => console.log('3')); // 微任务
console.log('4');                    // 同步
// 输出顺序:1, 4, 3, 2(同步先走完,再清微任务 3,再取宏任务 2)
  • 关键:微任务每轮宏任务后全部清空,优先级高于宏任务——这就是 Promise 总比 setTimeout 先执行的原因。
  • 本质:JS 的「并发」就是「单线程 + 两个队列轮流调度」,队列在这里承担了「异步回调的 FIFO 缓冲」。

七、队列应用选型表

场景用什么复杂度
树/图按层遍历、无权图最短路BFS(普通队列)O(V+E)
先到先服务的任务调度普通队列(FCFS)入队出队 O(1)
生产者-消费者缓冲阻塞队列O(1)(加锁另算)
跨进程解耦/削峰消息队列(Kafka/RabbitMQ)
按优先级出队(Dijkstra/Top-K)优先队列(堆)O(log n)
滑动窗口最值单调队列(deque)O(n)
JS 异步回调事件循环(宏+微队列)

交互演示

下一步

队列叶到此完成。下一站进入下一个基本数据结构——链表,体会它如何与队列互为实现载体;或回顾参考速查队列的 API、复杂度与易错点。