Queue (Очередь)
Queue — линейная структура данных, работающая по принципу FIFO (First In, First Out): первый добавленный элемент извлекается первым. Как очередь в магазине.
Зачем нужно
Очереди управляют порядком обработки задач. Event loop в JavaScript — очередь. Очередь сообщений, очередь печати, BFS (обход графов в ширину), rate limiting — везде очередь.
Где используется
- Event loop JavaScript (task queue, microtask queue)
- BFS — обход графов в ширину
- Очереди сообщений (RabbitMQ, Kafka)
- Буферизация (потоки данных)
- Планирование задач (task scheduler)
- Print queue, download queue
Предпосылки
Принцип FIFO
Enqueue 1: [1] ← rear (хвост)
Enqueue 2: [1, 2]
Enqueue 3: [1, 2, 3]
Dequeue: [2, 3] → 1 ← front (голова) — извлекли первый
Dequeue: [3] → 2
Peek: [3] → 3 ← посмотрели, не извлекая
Операции
| Операция | Описание | Сложность |
|---|---|---|
enqueue(item) |
Добавить в конец | O(1) |
dequeue |
Извлечь из начала | O(1)* |
peek / front |
Посмотреть первый | O(1) |
isEmpty |
Проверка пустоты | O(1) |
size |
Количество элементов | O(1) |
* O(1) для связного списка, O(n) для наивной реализации на массиве (shift)
Реализация в JavaScript
На основе объекта (O(1) для всех операций)
class Queue {
#items = {};
#head = 0;
#tail = 0;
enqueue(item) {
this.#items[this.#tail] = item;
this.#tail++;
}
dequeue {
if (this.isEmpty) throw new Error('Queue is empty');
const item = this.#items[this.#head];
delete this.#items[this.#head];
this.#head++;
return item;
}
peek {
if (this.isEmpty) throw new Error('Queue is empty');
return this.#items[this.#head];
}
isEmpty {
return this.#tail - this.#head === 0;
}
size {
return this.#tail - this.#head;
}
}
const q = new Queue();
q.enqueue('A');
q.enqueue('B');
q.enqueue('C');
console.log(q.dequeue); // 'A'
console.log(q.peek); // 'B'
console.log(q.size); // 2
На основе связного списка
class QueueNode {
constructor(value) {
this.value = value;
this.next() = null;
}
}
class LinkedQueue {
#head = null;
#tail = null;
#size = 0;
enqueue(value) {
const node = new QueueNode(value);
if (this.#tail) {
this.#tail.next() = node;
} else {
this.#head = node;
}
this.#tail = node;
this.#size++;
}
dequeue {
if (!this.#head) throw new Error('Queue is empty');
const value = this.#head.value;
this.#head = this.#head.next();
if (!this.#head) this.#tail = null;
this.#size--;
return value;
}
peek {
if (!this.#head) throw new Error('Queue is empty');
return this.#head.value;
}
isEmpty { return this.#size === 0; }
size { return this.#size; }
}
Priority Queue (приоритетная очередь)
Элементы извлекаются по приоритету, а не по порядку добавления.
class PriorityQueue {
#items = ;
enqueue(value, priority) {
const element = { value, priority };
// Найти позицию для вставки (sorted insert)
let added = false;
for (let i = 0; i < this.#items.length; i++) {
if (element.priority < this.#items[i].priority) {
this.#items.splice(i, 0, element);
added = true;
break;
}
}
if (!added) this.#items.push(element);
}
dequeue {
if (this.isEmpty) throw new Error('Queue is empty');
return this.#items.shift().value;
}
peek {
if (this.isEmpty) throw new Error('Queue is empty');
return this.#items[0].value;
}
isEmpty { return this.#items.length === 0; }
size { return this.#items.length; }
}
const pq = new PriorityQueue();
pq.enqueue('Низкий', 3);
pq.enqueue('Критичный', 1);
pq.enqueue('Средний', 2);
console.log(pq.dequeue); // 'Критичный' (приоритет 1)
console.log(pq.dequeue); // 'Средний' (приоритет 2)
Практические примеры
BFS — обход графа в ширину
function bfs(graph, start) {
const visited = new Set();
const queue = [start];
const result = ;
visited.add(start);
while (queue.length > 0) {
const node = queue.shift(); // dequeue
result.push(node);
for (const neighbor of graph[node] || ) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push(neighbor); // enqueue
}
}
}
return result;
}
const graph = {
A: ['B', 'C'],
B: ['D', 'E'],
C: ['F'],
D: , E: , F:
};
console.log(bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E', 'F']
Планировщик задач
class TaskScheduler {
#queue = new Queue();
#running = false;
add(task) {
this.#queue.enqueue(task);
if (!this.#running) this.#run;
}
async #run {
this.#running = true;
while (!this.#queue.isEmpty) {
const task = this.#queue.dequeue;
try {
await task;
} catch (err) {
console.error('Task failed:', err);
}
}
this.#running = false;
}
}
const scheduler = new TaskScheduler();
scheduler.add(async () => {
console.log('Задача 1');
await new Promise(r => setTimeout(r, 100));
});
scheduler.add(async => console.log('Задача 2'));
JavaScript Event Loop и очереди
┌────────────────────────────────────┐
│ Call Stack │
│ (стек вызовов — LIFO) │
└─────────────┬───────────────────���──┘
│
┌─────────────┴────────���─────────────┐
│ Event Loop │
│ Проверяет: стек пуст? │
│ Да → берёт из очереди │
└─────────┬────────────┬─────────────┘
│ │
┌─────────┴───┐ ┌─────┴──────────┐
│ Microtask │ │ Macrotask │
│ Queue │ │ Queue │
│ (Promise) │ │ (setTimeout) │
│ Приоритет! │ │ │
└─────────────┘ └────────────────┘
console.log('1');
setTimeout( => console.log('2'), 0); // macrotask queue
Promise.resolve.then( => console.log('3')); // microtask queue
console.log('4');
// Вывод: 1, 4, 3, 2
// Microtask queue обрабатывается ПЕРЕД macrotask queue
Частые ошибки
- Использовать
shiftдля очереди на массиве.shift— O(n)! Используйте объект или связный список - Путать стек и очередь. Stack — LIFO (стопка), Queue — FIFO (очередь)
- Не обрабатывать пустую очередь.
dequeueна пустой очереди — ошибка - Игнорировать microtask vs macrotask. Promise callbacks выполняются раньше setTimeout
Практика
- Реализуйте Queue на основе двух стеков (push в один, pop из другого)
- Реализуйте горячую клавишу последних N действий (circular buffer / ring queue)
- Реализуйте BFS для поиска кратчайшего пути в лабиринте
Связанные темы
- Stack (LIFO — противоположность)
- Array
- Linked List (основа для эффективной очереди)
- Graph (BFS использует очередь)
- Tree (BFS-обход дерева)
Ресурсы
- visualgo.net/en/list — визуализация очереди
- JavaScript Event Loop (Jake Archibald — доклад)
- learn.javascript.ru — Event loop