Очередь
Последнее обновление
У очереди два рабочих конца. Новые значения встают в конец, а покидают её с начала, поэтому первым обслуживают того, кто ждал дольше всех. Это и есть FIFO, и ровно так ведёт себя живая очередь у стойки: именно потому, что встают в хвост, а обслуживают с начала, ожидание остаётся честным. Нажмите «воспроизвести» выше и посмотрите, как значения входят с одной стороны и выходят с другой.
Поскольку у каждого конца свой индекс или указатель, обе операции выполняются за O(1) и ни одна из них не сдвигает остальные данные. Именно поэтому очередь лежит в основе всего, что обрабатывает работу в порядке поступления: заданий на печать, очередей задач и сообщений, буферов запросов и поиска в ширину, который обходит граф уровень за уровнем именно потому, что хранит свою границу в очереди. Перенесите конец, с которого удаляют, назад, и получится стек.
Временная и пространственная сложность
Для очереди на кольцевом буфере или на связном списке, двух стандартных реализаций:
| Операция | Сложность | Примечания |
|---|---|---|
| Enqueue (добавление в конец) | O(1) | Записать значение в конец и продвинуть индекс конца. |
| Dequeue (извлечение из начала) | O(1) | Прочитать значение в начале и продвинуть индекс начала, без сдвига остальных. |
| Peek (просмотр начала) | O(1) | Прочитать значение в начале, не удаляя его. |
| Поиск | O(n) | Очередь нужна не для этого: чтобы заглянуть внутрь, придётся её опустошить. |
| Память | O(n) | По одной ячейке на каждое ожидающее значение. |
Шаг за шагом
| Шаг | Что происходит |
|---|---|
| 1 | Очередь начинается пустой, начало и конец указывают на одну и ту же ячейку. |
| 2 | Enqueue записывает значение в конец, а затем сдвигает конец на единицу. |
| 3 | Каждое следующее добавление встаёт позади значений, которые уже ждут. |
| 4 | Dequeue читает значение в начале, а затем сдвигает начало на единицу. |
| 5 | Возвращается всегда то значение, которое ждало дольше всех. |
| 6 | Когда начало догоняет конец, очередь снова пуста, и дальнейшее извлечение будет ошибкой. |
Разобранный пример
Добавляем 3, 7, 5, а затем опустошаем очередь:
| Операция | Очередь (от начала к концу) | Возвращает |
|---|---|---|
enqueue(3) | [3] | ничего |
enqueue(7) | [3, 7] | ничего |
enqueue(5) | [3, 7, 5] | ничего |
dequeue() | [7, 5] | 3, самое старое значение |
dequeue() | [5] | 7 |
dequeue() | [] | 5, самое новое значение, последним |
Когда использовать очередь
| Используйте, когда | Избегайте, когда |
|---|---|
| Работу нужно обрабатывать в порядке поступления: очереди заданий, буферы запросов, спулеры печати | Вам нужен сначала самый свежий элемент, для этого есть стек |
| Вы исследуете данные уровень за уровнем, как это делает поиск в ширину | Элементы нужно обслуживать по приоритету, а не по времени прихода, и тогда подойдёт куча |
| Производитель и потребитель работают с разной скоростью, и между ними нужен буфер | Вам нужно искать или обращаться по индексу в середину данных |
Вам нужны вставка и удаление за O(1) без сдвига элементов | Вы стали бы реализовывать её сдвигом массива при каждом извлечении, из-за чего она станет O(n) |
Код Queue
Чистая, готовая к запуску реализация Queue на Python, JavaScript, Java, C++, C. Выберите язык, скопируйте код или откройте его уже загруженным в плейграунде Coddy.
Код Queue на Python
1from collections import deque2
3queue = deque()4
5# Enqueue three values at the rear6for value in [3, 7, 5]:7 queue.append(value)8 print(f"enqueue {value} -> {list(queue)}")9
10# Dequeue them from the front: first in, first out11while queue:12 value = queue.popleft()13 print(f"dequeue {value} -> {list(queue)}")14
15print("empty:", len(queue) == 0)Код Queue на JavaScript
1// A plain array makes dequeue O(n): shift() moves every element left.2// Track a head index instead, the fix the queue article describes.3const queue = { items: [], head: 0 };4
5function enqueue(value) {6 queue.items.push(value);7}8
9function dequeue() {10 const value = queue.items[queue.head];11 queue.items[queue.head] = undefined; // free the slot12 queue.head += 1;13 // Reclaim space once the consumed prefix dominates.14 if (queue.head * 2 >= queue.items.length) {15 queue.items = queue.items.slice(queue.head);16 queue.head = 0;17 }18 return value;19}20
21const size = () => queue.items.length - queue.head;22
23for (const value of [3, 7, 5]) {24 enqueue(value);25 console.log(`enqueue ${value} -> size ${size()}`);26}27
28// Dequeue from the front: first in, first out, amortized O(1)29while (size() > 0) {30 console.log(`dequeue ${dequeue()} -> size ${size()}`);31}32
33console.log('empty:', size() === 0);Код Queue на Java
1import java.util.ArrayDeque;2import java.util.Queue;3
4public class Main {5 public static void main(String[] args) {6 Queue<Integer> queue = new ArrayDeque<>();7
8 // Enqueue three values at the rear9 for (int value : new int[] {3, 7, 5}) {10 queue.add(value);11 System.out.println("enqueue " + value + " -> " + queue);12 }13
14 // Dequeue them from the front: first in, first out15 while (!queue.isEmpty()) {16 int value = queue.remove();17 System.out.println("dequeue " + value + " -> " + queue);18 }19
20 System.out.println("empty: " + queue.isEmpty());21 }22}Код Queue на C++
1#include <iostream>2#include <queue>3
4int main() {5 std::queue<int> queue;6
7 // Enqueue three values at the rear8 for (int value : {3, 7, 5}) {9 queue.push(value);10 std::cout << "enqueue " << value << " -> size " << queue.size() << "\n";11 }12
13 // Dequeue them from the front: first in, first out14 while (!queue.empty()) {15 int value = queue.front();16 queue.pop();17 std::cout << "dequeue " << value << " -> size " << queue.size() << "\n";18 }19
20 std::cout << "empty: " << std::boolalpha << queue.empty() << "\n";21 return 0;22}Код Queue на C
1#include <stdio.h>2
3#define CAP 164
5int queue[CAP];6int front = 0;7int rear = 0; /* index of the next free slot */8
9int main(void) {10 int values[3] = {3, 7, 5};11
12 /* Enqueue three values at the rear */13 for (int i = 0; i < 3; i++) {14 queue[rear++] = values[i];15 printf("enqueue %d -> size %d\n", values[i], rear - front);16 }17
18 /* Dequeue them from the front: first in, first out */19 while (front < rear) {20 int value = queue[front++];21 printf("dequeue %d -> size %d\n", value, rear - front);22 }23
24 printf("empty: %d\n", front == rear);25 return 0;26}Частые вопросы об очереди
Что означает FIFO?
Чем очередь отличается от стека?
O(1); очередь удаляет из начала (FIFO), а стек с того же конца, куда добавлял (LIFO). В остальном их таблицы сложности совпадают.Какие основные операции у очереди?
enqueue добавляет значение в конец, dequeue удаляет значение из начала и возвращает его, peek (или front) читает начало, не удаляя его, а is_empty сообщает, ждёт ли ещё что-нибудь. Все четыре выполняются за O(1).Почему извлечение медленное, если использовать обычный массив?
O(n). Настоящие реализации обходят это кольцевым буфером, который продвигает индекс начала, или связным списком с указателем на голову. collections.deque в Python и ArrayDeque в Java делают это за вас, а list.pop(0) нет.Что такое кольцевая очередь?
n работает сколько угодно долго, вместо того чтобы упереться в конец массива.