Menu
Coddy logo textTech

Очередь

Последнее обновление

У очереди два рабочих конца. Новые значения встают в конец, а покидают её с начала, поэтому первым обслуживают того, кто ждал дольше всех. Это и есть FIFO, и ровно так ведёт себя живая очередь у стойки: именно потому, что встают в хвост, а обслуживают с начала, ожидание остаётся честным. Нажмите «воспроизвести» выше и посмотрите, как значения входят с одной стороны и выходят с другой.

Поскольку у каждого конца свой индекс или указатель, обе операции выполняются за O(1) и ни одна из них не сдвигает остальные данные. Именно поэтому очередь лежит в основе всего, что обрабатывает работу в порядке поступления: заданий на печать, очередей задач и сообщений, буферов запросов и поиска в ширину, который обходит граф уровень за уровнем именно потому, что хранит свою границу в очереди. Перенесите конец, с которого удаляют, назад, и получится стек.

Временная и пространственная сложность

Для очереди на кольцевом буфере или на связном списке, двух стандартных реализаций:

ОперацияСложностьПримечания
Enqueue (добавление в конец)O(1)Записать значение в конец и продвинуть индекс конца.
Dequeue (извлечение из начала)O(1)Прочитать значение в начале и продвинуть индекс начала, без сдвига остальных.
Peek (просмотр начала)O(1)Прочитать значение в начале, не удаляя его.
ПоискO(n)Очередь нужна не для этого: чтобы заглянуть внутрь, придётся её опустошить.
ПамятьO(n)По одной ячейке на каждое ожидающее значение.

Шаг за шагом

ШагЧто происходит
1Очередь начинается пустой, начало и конец указывают на одну и ту же ячейку.
2Enqueue записывает значение в конец, а затем сдвигает конец на единицу.
3Каждое следующее добавление встаёт позади значений, которые уже ждут.
4Dequeue читает значение в начале, а затем сдвигает начало на единицу.
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

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)
Запустите этот код в плейграунде Python

Частые вопросы об очереди

Что означает FIFO?
First in, first out, то есть первым пришёл, первым вышел: следующим обслуживают то значение, которое ждало дольше всех. Привычная картинка для этого, очередь в билетную кассу. Стек работает по противоположному правилу, LIFO.
Чем очередь отличается от стека?
Только концом, с которого удаляют. Обе структуры добавляют в конец за O(1); очередь удаляет из начала (FIFO), а стек с того же конца, куда добавлял (LIFO). В остальном их таблицы сложности совпадают.
Какие основные операции у очереди?
enqueue добавляет значение в конец, dequeue удаляет значение из начала и возвращает его, peek (или front) читает начало, не удаляя его, а is_empty сообщает, ждёт ли ещё что-нибудь. Все четыре выполняются за O(1).
Почему извлечение медленное, если использовать обычный массив?
Потому что удаление элемента с индексом 0 сдвигает влево все оставшиеся элементы, из-за чего каждое извлечение стоит O(n). Настоящие реализации обходят это кольцевым буфером, который продвигает индекс начала, или связным списком с указателем на голову. collections.deque в Python и ArrayDeque в Java делают это за вас, а list.pop(0) нет.
Что такое кольцевая очередь?
Это очередь в массиве фиксированного размера, где индексы начала и конца, дойдя до края, снова переходят на 0. Она переиспользует ячейки, освобождённые извлечениями, поэтому очередь ёмкостью n работает сколько угодно долго, вместо того чтобы упереться в конец массива.
Где очереди применяются в реальных программах?
Очереди задач и сообщений между сервисами, спулеры печати и заданий, буферы запросов в веб-серверах, буферы клавиатуры и событий, конвейеры «производитель-потребитель», а также поиск в ширину, в котором именно очередь заставляет обход идти уровень за уровнем.
Coddy programming languages illustration

Освойте алгоритмы с Coddy

НАЧАТЬ