Дек или двусторонняя очередь — структура данных, в которой элементы
можно добавлять и удалять с обоих концов.
Название происходит от:
Deque = Double Ended Queue
У дека есть два конца:
front -> начало
back -> конец
Пример:
deque = [7, 3, 9, 1]
front back
7 3 9 1
В дек можно добавить элемент слева:
pushFront(5)
deque = [5, 7, 3, 9, 1]
И справа:
pushBack(4)
deque = [5, 7, 3, 9, 1, 4]
Удаление тоже возможно с двух сторон:
popFront() -> 5
popBack() -> 4
Термины
deque -> двусторонняя очередь
front -> первый элемент
back -> последний элемент
size -> количество элементов
pushFront(x) -> добавить x в начало
pushBack(x) -> добавить x в конец
popFront() -> удалить и вернуть первый элемент
popBack() -> удалить и вернуть последний элемент
peekFront() -> посмотреть первый элемент без удаления
peekBack() -> посмотреть последний элемент без удаления
isEmpty() -> проверить, пуст ли дек
Дек задаётся тремя вещами:
1. набор элементов
2. порядок элементов
3. два доступных конца: front и back
Структура дека
Структура отвечает на вопрос:
Что внутри и как расположено?
У дека есть:
элементы
начало
конец
порядок
размер
Дек можно представить как горизонтальную последовательность с доступом с двух
сторон:
front back
↓ ↓
[7] <-> [3] <-> [9] <-> [1]
Основное отличие от стека:
stack -> доступ только к верхушке
deque -> доступ к началу и концу
Основное отличие от обычной очереди:
queue -> добавление с одного конца, удаление с другого
deque -> добавление и удаление с обоих концов
Свойства
Короткая карта свойств дека как структуры:
два конца -> front и back
упорядоченность -> порядок элементов имеет значение
двусторонний доступ -> операции доступны с начала и с конца
размер -> количество элементов в деке
пустота -> дек может не содержать элементов
ограниченный доступ -> произвольный доступ по индексу не является базовой операцией
Конкретный дек содержит конечное число элементов:
size = n
пустой дек = size == 0
непустой дек = size > 0
Для непустого дека существуют оба конца:
front(deque)
back(deque)
Если в деке один элемент, начало и конец указывают на один и тот же элемент:
deque = [7]
front = 7
back = 7
Важно отделять свойства дека от характеристик конкретного дека:
свойство: у дека есть начало
характеристика: первый элемент этого дека = 7
свойство: у дека есть размер
характеристика: размер этого дека = 4
Два конца
Дек предоставляет доступ к двум крайним позициям:
front(deque)
back(deque)
Для дека:
[7, 3, 9, 1]
получаем:
front = 7
back = 1
Порядок
Порядок элементов является частью дека.
[7, 3, 9, 1] != [1, 9, 3, 7]
Операции с разных концов по-разному меняют порядок:
pushFront(5) -> [5, 7, 3, 9, 1]
pushBack(5) -> [7, 3, 9, 1, 5]
Пустота
Дек может быть пустым:
deque = []
size = 0
Для пустого дека нельзя корректно выполнить:
popFront()
popBack()
peekFront()
peekBack()
если реализация явно не задаёт специальное поведение для этих случаев.
Базовые операции
Создание
deque = []
Обычно:
O(1)
Push Front
Добавить элемент в начало:
pushFront(deque, x)
Пример:
deque = [7, 3, 9]
pushFront(1)
deque = [1, 7, 3, 9]
Обычно:
O(1)
Push Back
Добавить элемент в конец:
pushBack(deque, x)
Пример:
deque = [7, 3, 9]
pushBack(1)
deque = [7, 3, 9, 1]
Обычно:
O(1)
Pop Front
Удалить и вернуть первый элемент:
popFront(deque)
Пример:
deque = [7, 3, 9, 1]
popFront() -> 7
deque = [3, 9, 1]
Условие:
size(deque) > 0
Обычно:
O(1)
Pop Back
Удалить и вернуть последний элемент:
popBack(deque)
Пример:
deque = [7, 3, 9, 1]
popBack() -> 1
deque = [7, 3, 9]
Условие:
size(deque) > 0
Обычно:
O(1)
Peek Front / Peek Back
Посмотреть крайний элемент без удаления:
peekFront(deque)
peekBack(deque)
Пример:
deque = [7, 3, 9, 1]
peekFront() -> 7
peekBack() -> 1
deque = [7, 3, 9, 1]
Сложность:
O(1)
Is Empty
Проверить, пуст ли дек:
isEmpty(deque)
Пример:
[] -> true
[7, 3, 9] -> false
Сложность:
O(1)
Size
Получить количество элементов:
size(deque)
Обычно:
O(1)
если размер хранится отдельно.
Проход по деку
Если нужно обработать все элементы через операции дека, его можно постепенно
разбирать с одной стороны:
while not isEmpty(deque):
x = popFront(deque)
use x
или с другой:
while not isEmpty(deque):
x = popBack(deque)
use x
Сложность:
O(n)
Важно: такой проход изменяет дек, потому что popFront() и popBack() удаляют
элементы.
Таблица сложностей
| Операция | Сложность |
|---|---|
| создание | O(1) |
pushFront(x) |
O(1) |
pushBack(x) |
O(1) |
popFront() |
O(1) |
popBack() |
O(1) |
peekFront() |
O(1) |
peekBack() |
O(1) |
isEmpty() |
O(1) |
size() |
O(1) |
| проход с удалением всех элементов | O(n) |
| поиск произвольного значения | O(n) |
Реализация
Дек — это абстрактная структура данных. Его можно реализовать разными способами.
Через двусвязный список
Каждый узел хранит ссылки на следующий и предыдущий узел:
front back
↓ ↓
[7] <-> [3] <-> [9] <-> [1]
Тогда:
pushFront(x) -> добавить новый узел перед front
pushBack(x) -> добавить новый узел после back
popFront() -> удалить узел front
popBack() -> удалить узел back
Все основные операции с концами работают за O(1), если есть ссылки на front
и back.
Через кольцевой буфер
Дек можно хранить в массиве фиксированной ёмкости с двумя указателями:
capacity = 8
front = 2
back = 5
index: 0 1 2 3 4 5 6 7
value: _ _ 7 3 9 1 _ _
При достижении конца массива индекс переходит в начало:
next(i) = (i + 1) mod capacity
prev(i) = (i - 1 + capacity) mod capacity
Такой вариант часто используется в стандартных библиотеках.
Если буфер заполнен, реализация может создать новый буфер большей ёмкости и
перенести элементы. Тогда отдельное расширение стоит O(n), но операции
добавления остаются амортизированно быстрыми.
Дек, стек и очередь
Дек может работать как стек:
pushBack(x)
popBack()
или:
pushFront(x)
popFront()
Дек может работать как очередь:
pushBack(x)
popFront()
или в обратном направлении:
pushFront(x)
popBack()
Поэтому дек часто используют как универсальную структуру для задач, где нужны
быстрые операции с обоих концов.
Python
В Python дек представлен типом:
collections.deque
Основные операции:
append(x) -> добавить справа
appendleft(x) -> добавить слева
pop() -> удалить справа
popleft() -> удалить слева
Пример:
from collections import deque
d = deque()
d.append(7)
d.append(3)
d.appendleft(1)
print(d) # deque([1, 7, 3])
print(d.pop()) # 3
print(d.popleft()) # 1
Типичные применения
очередь задач
скользящее окно
монотонная очередь
BFS
хранение последних n событий
палиндромы
симуляции с обработкой с двух концов
Формальная схема
Deque = {
elements: e1, e2, ..., en,
front: e1,
back: en,
size: n
}
Операции дека изменяют или используют одну из четырёх сущностей:
начало
конец
размер
порядок
Примеры:
pushFront(x) -> меняет начало и увеличивает размер
pushBack(x) -> меняет конец и увеличивает размер
popFront() -> возвращает начало и уменьшает размер
popBack() -> возвращает конец и уменьшает размер
peekFront() -> читает начало без изменения дека
peekBack() -> читает конец без изменения дека
Минимальная формулировка:
дек = структура с быстрым доступом к обоим концам