Персистентный дек
НЕТ ВОЙНЕ |
24 февраля 2022 года российское руководство во главе с Владимиром Путиным развязало агрессивную войну против Украины. В глазах всего мира это военное преступление совершено от лица всей страны, всех россиян. Будучи гражданами Российской Федерации, мы против своей воли оказались ответственными за нарушение международного права, военное вторжение и массовую гибель людей. Чудовищность совершенного преступления не оставляет возможности промолчать или ограничиться пассивным несогласием. Мы убеждены в абсолютной ценности человеческой жизни, в незыблемости прав и свобод личности. Режим Путина — угроза этим ценностям. Наша задача — обьединить все силы для сопротивления ей. Эту войну начали не россияне, а обезумевший диктатор. И наш гражданский долг — сделать всё, чтобы её остановить. Антивоенный комитет России |
Распространяйте правду о текущих событиях, оберегайте от пропаганды своих друзей и близких. Изменение общественного восприятия войны - ключ к её завершению. |
meduza.io, Популярная политика, Новая газета, zona.media, Майкл Наки. |
Дек (англ. deque — double ended queue — очередь с двумя концами) — структура данных с двусторонним доступом к элементам, т.е. их можно удалять и добавлять как в начало, так и в конец дека.
Кроме дека ещё существует структура данных, называемая steque, которая представляет собой объединение стека и очереди — элементы можно добавлять только в один конец, а извлекать — с обоих.
Содержание
Эффективная реализация
Способ хранения структуры
Персистентный дек можно визуально представить как односвязный список, где каждый узел хранит пару — левый элемент и правый, а также ребёнка — ссылку на следующий узел. Только левый и правый элемент каждого узла хранят в два раза больше объектов, чем предыдущий. Это удобно сделать с помощью типа пара.
Тип
class Pair<T>: T first T last
Сам дек можно инициализировать напрямую, вызвав конструктор
class Deque<T>: T left T right Deque<Pair<T>> child
Наша структура данных персистентна, следовательно операция
возвращает новый дек c элементом , добавленным в начало .push
Пустой дек будем обозначать значком
Deque<T> pushFront(x: T, D: Deque<T>): if D ==// если дек пустой, то формируем новый дек return Deque(x, ) else if D.left == // если левый ребенок не существует, то сделаем левым ребёнком return Deque(x, D.child, D.right) else // иначе объединим левого ребёнка с новым элементом и попытаемся добавить в дек на следующем уровне return Deque( , pushFront(Pair(x, D.left), D.child), D.right)
pop
Метод
Pair<T, Deque<T>> popFront(D: Deque<T>): if D ==// изъятие элемента из пустого дека возвращает пару "нулевой элемент — пустой дек" return else if D.left // если левый ребёнок не пуст, то возвращаем пару из него и нового дека без левого ребёнка, // но если остался только левый ребёнок, то возвращаем его и пустой дек if D.child == and D.right == return D.left, return D.left, Deque( , D.child, D.right) else if D.child == // если левый ребёнок оказался пуст, и при этом ссылка на следующий дек отсутствует, // то вернём пару из правого ребёнка и абсолютно пустого дека return D.right, else /* * если два предыдущих условия оказались не выполнены, то мы рекурсивно вызываем метод * и возвращённую пару "элемент — новый дек" сохраняем в переменные и . * Рекурсивные вызовы прекратятся, как только левый ребёнок окажется не пустым * или в деке будет отсутствовать ссылка на следующий дек. */ (temp, newDeque) = popFront(D.child) /* * здесь всегда не пуст; надо вернуть первый элемент пары temp * (в этом блоке temp всегда будет иметь тип ); * в качестве left нового дека надо поставить (на уровне ниже хранил * в два раза больше элементов, поэтому на текущем уровне будет соответствовать * требуемому количеству элементов); делаем 'ом * нового дека, а текущего 'ом нового */ return temp.first, Deque(temp.last, newDeque, D.right)
Операции добавления в правый конец и извлечение из него делаются симметрично.
Асимптотика времени работы
Таким образом, если мы добавляем элементы только в один конец, то на
-ом уровне дека не более элементов. Пусть глубина текущего дека Тогда в нём может находится не более объектов, откуда получаем .Чтобы извлечь элемент, придётся спуститься не больше, чем на глубину дерева. Аналогично для добавления. Поэтому обе операции выполняются за
Пример
Рассмотрим поподробнее операции. В худшем случае элементы будут добавляться только в один конец, а извлекаться из другого.
pushFront
Изначально у нас пустой дек. Элементы будем добавлять в левый конец дека и нумеровать согласно порядку добавления. Сначала добавим первый элемент. Он встанет на позицию левого ребёнка первого уровня дека. Теперь попытаемся добавить второй элемент. Позиция левого ребёнка занята, значит, мы объединяем новый элемент со старым и ставим сформированную пару на место левого ребёнка второго дека. Процесс добавления можно представить, как прибавление
к разряду числа в двоичном представлении: если в разряде , то подряд идущие за ней единицы обнулятся — элементы объединяются в пары, иначе становятся на место этого разряда.popFront
Посмотрим, как работает
на примере дека, симметричного нашему предыдущему (в рисунке надо понять, что элементы хранятся слева направо: ).Запустим первый
. Он будет рекурсивно вызывать сам себя, пока не дойдёт до последней ветки. Тогда в пару сохранятся две пары элементов и пустой дек высоты . Так как не пуст, то в предыдущий рекурсивный вызов вернётся пара: в новый сохранится пара , на текущем уровне будет пара , а будет ссылаться на пустой дек. Потом пара передастся выше, сохраняет ссылку на предыдущий дек, и, наконец, извлечётся. И так далее. Аналогично добавлению, процесс извлечения элемента можно представить, как вычитание из младшего разряда двоичного числа.