Персистентный дек — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Эффективная реализация)
(не показаны 22 промежуточные версии 2 участников)
Строка 1: Строка 1:
 +
'''Дек''' (англ. ''deque'' {{---}} double ended queue {{---}} очередь с двумя концами) {{---}} структура данных с двусторонним доступом к элементам, т.е. их можно удалять и добавлять как в начало, так и в конец дека.
  
{{Определение
+
Кроме дека ещё существует структура данных, называемая ''steque'', которая представляет собой объединение стека и очереди {{---}} элементы можно добавлять только в один конец, а извлекать {{---}} с обоих.
|definition =
 
'''Дек''' (англ. deque {{---}} double ended queue {{---}} очередь с двумя концами) {{---}} структура данных с двусторонним доступом к элементам, т.е. их можно удалять и добавлять как в начало, так и в конец дека.
 
}}
 
[[Файл:Deque.png|thumb|220px|Дек]]
 
Кроме дека ещё существует структура данных, называемая ''steque'', которая представляет собой объединение стека и очереди - элементы можно добавлять только в один конец, а извлекать {{---}} с обоих.
 
  
 
== Эффективная реализация ==
 
== Эффективная реализация ==
[[Файл:Tree_deque.png|thumb|220px|right|Древовидная структура дека]]
+
=== Способ хранения структуры ===
Написать дек на массиве не представляет особого труда, зная уже, как работают стек и очередь. Далее будет приведена реализация операций добавления элемента и извлечения из одного конца дека за истинное время работы <tex> O(\log ~ n). </tex> Добавление и исключение из другого конца делается симметрично.
+
[[Файл:Tree_deque.png|220px|right]]
  
Персистентный дек можно визуально представить как дерево, где каждый узел хранит пару - левый элемент и правый, а также ''ребёнка'' - ссылку на следующий дек. Только с каждым уровнем вложенности левый и правый элемент хранят в два раза больше объектов, чем на предыдущем уровне.
+
Персистентный дек можно визуально представить как [[Список#Односвязный список | односвязный список]], где каждый узел хранит пару {{---}} левый элемент и правый, а также ''ребёнка'' {{---}} ссылку на следующий узел. Только левый и правый элемент каждого узла хранят в два раза больше объектов, чем предыдущий. Это удобно сделать с помощью типа ''пара''.  
  
Тип <tex>Pair</tex> хранит пару элементов <tex>first</tex> и <tex>last</tex> типов <tex>T_1</tex> и <tex>T_2</tex> соответственно.
+
Тип <tex>\mathrm{Pair}</tex> хранит пару элементов <tex>\mathrm{first}</tex> и <tex>\mathrm{last}</tex> типов <tex>T</tex>.
 
<code style = "display: inline-block;">
 
<code style = "display: inline-block;">
   Pair<<tex>T_1, ~T_2</tex>> {
+
   '''class''' Pair<T>:
     <tex>T_1 ~first;</tex>
+
     T first
     <tex>T_2 ~last;</tex>
+
     T last
  };
 
 
</code>
 
</code>
Сам дек можно инициализировать напрямую, вызвав конструктор <tex>Deque(left, ~child, ~right)</tex>, или через шаблоны <tex>Deque<Pair<T_1, ~T_2>></tex>, тогда произойдёт следующее:
+
Сам дек можно инициализировать напрямую, вызвав конструктор <tex>\mathrm{Deque(left, ~child, ~right)}</tex>. Описание структуры через шаблон <tex>\mathrm{Deque{<}T{>}}</tex> следующее:
 
<code style = "display: inline-block;">
 
<code style = "display: inline-block;">
   Deque<Pair<<tex> T_1, ~T_2 </tex>>> {
+
   '''class''' Deque<T>:
     <tex>T_1</tex> left;
+
     T left
     <tex>T_2</tex> right;
+
     T right
     Deque<Pair<Pair<<tex> T_1, ~T_2 </tex>>, Pair<<tex> T_1, ~T_2 </tex>>> child;
+
     Deque<Pair<T>> child
  };
 
 
</code>
 
</code>
Таким образом элементы с начала хранятся в левой ветке дерева, а с конца - в правой.
+
Наша структура данных персистентна, следовательно операция <tex> \mathrm{pushFront(x, ~D)} </tex> возвращает новый дек <tex> D' </tex> c элементом <tex> x </tex>, добавленным в начало <tex> D </tex>.
  
Наша структура данных персистентна, следовательно операция <tex> D.push\_front(x) </tex> возвращает новый дек <tex> D' </tex> c элементом <tex> x </tex> в начале.
+
=== push ===
 +
Пустой дек будем обозначать значком <tex> \emptyset </tex>, а пустую пару {{---}} <tex> \varnothing </tex>, чтобы было яснее, когда мы обращаемся к деку, а когда к элементу. Когда мы хотим добавить элемент в начало дека или удалить из начала, то прежде всего обращаемся к полю <tex>\mathrm{left}</tex>. Если же работаем с концом, то обращаемся сперва к полю <tex>\mathrm{right}</tex>.
 
<code>
 
<code>
   push_front(x)
+
   '''Deque<T>''' pushFront(x: '''T''', D: '''Deque<T>'''):
     if left == <tex> \varnothing </tex>
+
    '''if''' D == <tex> \emptyset </tex>
       // если левый ребенок не существует, то сделаем его новым элементом
+
      <font color=darkgreen>// если дек пустой, то формируем новый дек </font>
       return Deque(x, child, right)
+
      '''return''' Deque(x, <tex> \emptyset ,~ \varnothing </tex>)
     else
+
     '''else if''' D.left == <tex> \varnothing </tex>
       // иначе объединим его с новым элементов и попытаемся добавить в дек на следующем уровне
+
       <font color=darkgreen>// если левый ребенок не существует, то сделаем <tex> x </tex> левым ребёнком </font>
       return Deque(<tex> \varnothing </tex>, child.push_front(Pair<x, left>), right)
+
       '''return''' Deque(x, D.child, D.right)
 +
     '''else'''
 +
       <font color=darkgreen>// иначе объединим левого ребёнка с новым элементом и попытаемся добавить в дек на следующем уровне </font>
 +
       '''return''' Deque(<tex> \varnothing </tex>, pushFront(Pair(x, D.left), D.child), D.right)
 
</code>
 
</code>
  
Метод <tex> D.pop\_front() </tex> возвращает пару <tex> \left \langle e, ~D' \right \rangle </tex> из первого элемента и нового дека, полученного из старого изъятием этого элемента.
+
=== pop ===
 +
Метод <tex> \mathrm{popFront(D)} </tex> возвращает пару <tex> \left \langle e, ~D' \right \rangle </tex> из первого элемента и нового дека, полученного из старого изъятием этого элемента.
 
<code>
 
<code>
   pop_front()
+
   '''Pair<T, Deque<T>>''' popFront(D: '''Deque<T>'''):
     if left <tex> \neq ~\varnothing</tex>
+
    '''if''' D == <tex> \emptyset </tex>
       // если левый ребёнок не пуст, то возвращаем пару из него и нового дека без левого ребёнка
+
      <font color=darkgreen>// изъятие элемента из пустого дека возвращает пару "нулевой элемент {{---}} пустой дек"</font>
       return <tex> \mathcal{h} </tex>left, Deque(<tex> \varnothing </tex>, child, right)<tex> \mathcal{i} </tex>
+
      '''return''' <tex> \mathcal{h} \varnothing ,~ \emptyset \mathcal{i} </tex>
     else if child == <tex> \varnothing</tex>
+
     '''else if''' D.left <tex> \neq ~\varnothing</tex>
       // если левый ребёнок оказался пуст, и при этом ссылка на следующий дек отсутствует,
+
       <font color=darkgreen>// если левый ребёнок не пуст, то возвращаем пару из него и нового дека без левого ребёнка,
       // то вернём пару из правого ребёнка и абсолютно пустого дека
+
      // но если остался только левый ребёнок, то возвращаем его и пустой дек</font>
       return <tex> \mathcal{h} </tex>right, Deque(<tex> \varnothing ,~\varnothing ,~\varnothing </tex>)<tex> \mathcal{i} </tex>
+
       '''if''' D.child == <tex> \emptyset </tex> '''and''' D.right == <tex> \varnothing </tex>
     else
+
        '''return''' <tex> \mathcal{h} </tex>D.left, <tex> \emptyset \mathcal{i} </tex>
       /*  
+
      '''return''' <tex> \mathcal{h} </tex>D.left, Deque(<tex> \varnothing </tex>, D.child, D.right)<tex> \mathcal{i} </tex>
       * если два предыдущих условия оказались не выполнены, то мы рекурсивно вызываем метод pop_front()
+
     '''else if''' D.child == <tex> \varnothing</tex>
       * и возвращённую пару "элемент-новый дек" сохраняем в переменные temp & newDeque
+
       <font color=darkgreen>// если левый ребёнок оказался пуст, и при этом ссылка на следующий дек отсутствует,
       * Рекурсивные вызовы прекратятся, как только левый ребёнок окажется существующим
+
       // то вернём пару из правого ребёнка и абсолютно пустого дека</font>
       * или в деке будет отсутствовать ссылка на следующий дек
+
       '''return''' <tex> \mathcal{h} </tex>D.right, <tex>\emptyset \mathcal{i} </tex>
       */     
+
     '''else'''
       temp, newDeque <tex> \leftarrow </tex> child.pop_front()
+
       <font color=darkgreen>/*  
       if temp == <tex> \varnothing </tex>
+
       * если два предыдущих условия оказались не выполнены, то мы рекурсивно вызываем метод <tex>\mathrm{popFront}</tex>
        /*
+
       * и возвращённую пару "элемент {{---}} новый дек" сохраняем в переменные <tex>temp</tex> и <tex>newDeque</tex>.
        * это возможно только тогда, когда в деке на максимальной глубине все элементы оказались пусты,
+
       * Рекурсивные вызовы прекратятся, как только левый ребёнок окажется не пустым
        * значит, мы сейчас на предпоследнем уровне, левый ребёнок пустой и child ссылается на абсолютно пустой дек,
+
       * или в деке будет отсутствовать ссылка на следующий дек.
        * поэтому возвращаем right текущего дека и пустой дек
+
       */</font>      
        */
+
       (temp, newDeque) = popFront(D.child)
        return <tex> \mathcal{h} </tex>right, Deque(<tex> \varnothing ,~\varnothing ,~\varnothing </tex>)<tex> \mathcal{i} </tex>
+
       <font color=darkgreen>/*
      else
+
      * здесь <tex>temp</tex> всегда не пуст; надо вернуть первый элемент пары temp
        /*
+
      * (в этом блоке temp всегда будет иметь тип <tex>\mathrm{Pair}</tex>);
        * если всё же temp не пуст, то надо вернуть первый элементы пары temp;  
+
      * в качестве left нового дека надо поставить <tex>temp.last</tex> (на уровне ниже <tex>temp</tex> хранил
        * в качестве left нового дека надо поставить temp.last (на уровне ниже temp хранил
+
      * в два раза больше элементов, поэтому на текущем уровне <tex>temp.last</tex> будет соответствовать  
        * в два раза больше элементов, поэтому на текущем уровне temp.last будет соответствовать  
+
      * требуемому количеству элементов); <tex>newDeque</tex> делаем <tex>child</tex>'ом
        * требуемому количеству элементов); newDeque делаем child'ом
+
      * нового дека, а <tex>right</tex> текущего <tex>right</tex>'ом нового
        * нового дека, а right текущего right'ом нового
+
      */</font>
        */
+
      '''return''' <tex> \langle </tex>temp.first, Deque(temp.last, newDeque, D.right)<tex> \rangle </tex>
        return <tex> \mathcal{h} </tex>temp.first, Deque(temp.last, newDeque, right)<tex> \mathcal{i} </tex>
 
 
</code>
 
</code>
  
Чтобы извлечь элемент, придётся спуститься не больше, чем на глубину дерева. Аналогично для добавления. Поэтому обе операции выполняются за <tex> O(\log ~n) </tex>
+
Операции добавления в правый конец и извлечение из него делаются симметрично.
  
 +
=== Асимптотика времени работы ===
 +
Таким образом, если мы добавляем элементы только в один конец, то на <tex> i </tex>-ом уровне дека не более <tex> 2^i </tex> элементов. Пусть глубина текущего дека <tex> d. </tex> Тогда в нём может находится не более <tex> n = 1 + 2 + 4 + \ldots + 2^d </tex> объектов, откуда получаем <tex> d = \mathcal{b} \log_2 n \mathcal{c}  </tex>.
 +
 +
Чтобы извлечь элемент, придётся спуститься не больше, чем на глубину дерева. Аналогично для добавления. Поэтому обе операции выполняются за <tex> \mathcal{O}(\log n) </tex>
 +
 +
== Пример ==
 +
 +
Рассмотрим поподробнее операции. В худшем случае элементы будут добавляться только в один конец, а извлекаться из другого.
 +
 +
=== pushFront ===
 +
 +
Изначально у нас пустой дек. Элементы будем добавлять в левый конец дека и нумеровать согласно порядку добавления. Сначала добавим первый элемент. Он встанет на позицию левого ребёнка первого уровня дека. Теперь попытаемся добавить второй элемент. Позиция левого ребёнка занята, значит, мы объединяем новый элемент со старым и ставим сформированную пару на место левого ребёнка второго дека. Процесс добавления можно представить, как прибавление <tex>1</tex> к разряду числа в двоичном представлении: если в разряде <tex>1</tex>, то подряд идущие за ней единицы обнулятся {{---}} элементы объединяются в пары, иначе становятся на место этого разряда.
 +
 +
[[Файл:PushDequeExample.png|700px]]
 +
 +
=== popFront ===
 +
 +
Посмотрим, как работает <tex>\mathrm{popFront}</tex> на примере дека, симметричного нашему предыдущему (в рисунке надо понять, что элементы хранятся слева направо: <tex>4 ~3 ~2 ~1</tex>).
 +
 +
Запустим первый <tex> \mathrm{popFront} </tex>. Он будет рекурсивно вызывать сам себя, пока не дойдёт до последней ветки. Тогда в пару <tex> \left \langle temp, ~newDeque \right \rangle </tex> сохранятся две пары элементов и пустой дек высоты <tex>0</tex>. Так как <tex> temp </tex> не пуст, то в предыдущий рекурсивный вызов вернётся пара: в <tex>temp</tex> новый сохранится пара <tex>4 ~3</tex>, на текущем уровне будет пара <tex>2 ~1</tex>, а <tex> child </tex> будет ссылаться на пустой дек. Потом пара <tex>4 ~3</tex> передастся выше, <tex>child</tex> сохраняет ссылку на предыдущий дек, и, наконец, <tex>4</tex> извлечётся. И так далее. Аналогично добавлению, процесс извлечения элемента можно представить, как вычитание <tex>1</tex> из младшего разряда двоичного числа.
 +
 +
[[Файл:PopDequeExample.png|700px]]
 
== См. также ==
 
== См. также ==
  
 +
* [[Список]]
 +
* [[Стек]]
 
* [[Очередь]]
 
* [[Очередь]]
* [[Стек]]
 
 
* [[Персистентный стек]]
 
* [[Персистентный стек]]
  
== Ссылки ==
+
== Источники информации ==
  
 
* [http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.13.3451 Purely Functional Deques with Catenation by Robert E. Tarjan]
 
* [http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.13.3451 Purely Functional Deques with Catenation by Robert E. Tarjan]
Строка 91: Строка 112:
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Амортизационный анализ]]
 
[[Категория: Амортизационный анализ]]
 +
[[Категория: Структуры данных]]

Версия 20:33, 1 мая 2016

Дек (англ. deque — double ended queue — очередь с двумя концами) — структура данных с двусторонним доступом к элементам, т.е. их можно удалять и добавлять как в начало, так и в конец дека.

Кроме дека ещё существует структура данных, называемая steque, которая представляет собой объединение стека и очереди — элементы можно добавлять только в один конец, а извлекать — с обоих.

Эффективная реализация

Способ хранения структуры

Tree deque.png

Персистентный дек можно визуально представить как односвязный список, где каждый узел хранит пару — левый элемент и правый, а также ребёнка — ссылку на следующий узел. Только левый и правый элемент каждого узла хранят в два раза больше объектов, чем предыдущий. Это удобно сделать с помощью типа пара.

Тип [math]\mathrm{Pair}[/math] хранит пару элементов [math]\mathrm{first}[/math] и [math]\mathrm{last}[/math] типов [math]T[/math].

 class Pair<T>:
   T first
   T last

Сам дек можно инициализировать напрямую, вызвав конструктор [math]\mathrm{Deque(left, ~child, ~right)}[/math]. Описание структуры через шаблон [math]\mathrm{Deque{\lt }T{\gt }}[/math] следующее:

 class Deque<T>:
   T left
   T right
   Deque<Pair<T>> child

Наша структура данных персистентна, следовательно операция [math] \mathrm{pushFront(x, ~D)} [/math] возвращает новый дек [math] D' [/math] c элементом [math] x [/math], добавленным в начало [math] D [/math].

push

Пустой дек будем обозначать значком [math] \emptyset [/math], а пустую пару — [math] \varnothing [/math], чтобы было яснее, когда мы обращаемся к деку, а когда к элементу. Когда мы хотим добавить элемент в начало дека или удалить из начала, то прежде всего обращаемся к полю [math]\mathrm{left}[/math]. Если же работаем с концом, то обращаемся сперва к полю [math]\mathrm{right}[/math].

 Deque<T> pushFront(x: T, D: Deque<T>):
   if D == [math] \emptyset [/math]
     // если дек пустой, то формируем новый дек 
     return Deque(x, [math] \emptyset ,~ \varnothing [/math])
   else if D.left == [math] \varnothing [/math]
     // если левый ребенок не существует, то сделаем [math] x [/math] левым ребёнком 
     return Deque(x, D.child, D.right)
   else
     // иначе объединим левого ребёнка с новым элементом и попытаемся добавить в дек на следующем уровне 
     return Deque([math] \varnothing [/math], pushFront(Pair(x, D.left), D.child), D.right)

pop

Метод [math] \mathrm{popFront(D)} [/math] возвращает пару [math] \left \langle e, ~D' \right \rangle [/math] из первого элемента и нового дека, полученного из старого изъятием этого элемента.

 Pair<T, Deque<T>> popFront(D: Deque<T>):
   if D == [math] \emptyset [/math]
     // изъятие элемента из пустого дека возвращает пару "нулевой элемент — пустой дек"
     return [math] \mathcal{h} \varnothing ,~ \emptyset \mathcal{i} [/math]
   else if D.left [math] \neq ~\varnothing[/math]
     // если левый ребёнок не пуст, то возвращаем пару из него и нового дека без левого ребёнка,
     // но если остался только левый ребёнок, то возвращаем его и пустой дек
     if D.child == [math] \emptyset [/math] and D.right == [math] \varnothing [/math]
       return [math] \mathcal{h} [/math]D.left, [math] \emptyset \mathcal{i} [/math]
     return [math] \mathcal{h} [/math]D.left, Deque([math] \varnothing [/math], D.child, D.right)[math] \mathcal{i} [/math]
   else if D.child == [math] \varnothing[/math]
     // если левый ребёнок оказался пуст, и при этом ссылка на следующий дек отсутствует,
     // то вернём пару из правого ребёнка и абсолютно пустого дека
     return [math] \mathcal{h} [/math]D.right, [math]\emptyset \mathcal{i} [/math]
   else
     /* 
      * если два предыдущих условия оказались не выполнены, то мы рекурсивно вызываем метод [math]\mathrm{popFront}[/math]
      * и возвращённую пару "элемент — новый дек" сохраняем в переменные [math]temp[/math] и [math]newDeque[/math].
      * Рекурсивные вызовы прекратятся, как только левый ребёнок окажется не пустым 
      * или в деке будет отсутствовать ссылка на следующий дек.
      */     
     (temp, newDeque) = popFront(D.child)
     /*
      * здесь [math]temp[/math] всегда не пуст; надо вернуть первый элемент пары temp
      * (в этом блоке temp всегда будет иметь тип [math]\mathrm{Pair}[/math]);
      * в качестве left нового дека надо поставить [math]temp.last[/math] (на уровне ниже [math]temp[/math] хранил
      * в два раза больше элементов, поэтому на текущем уровне [math]temp.last[/math] будет соответствовать 
      * требуемому количеству элементов); [math]newDeque[/math] делаем [math]child[/math]'ом
      * нового дека, а [math]right[/math] текущего [math]right[/math]'ом нового
      */
     return [math] \langle [/math]temp.first, Deque(temp.last, newDeque, D.right)[math] \rangle [/math]

Операции добавления в правый конец и извлечение из него делаются симметрично.

Асимптотика времени работы

Таким образом, если мы добавляем элементы только в один конец, то на [math] i [/math]-ом уровне дека не более [math] 2^i [/math] элементов. Пусть глубина текущего дека [math] d. [/math] Тогда в нём может находится не более [math] n = 1 + 2 + 4 + \ldots + 2^d [/math] объектов, откуда получаем [math] d = \mathcal{b} \log_2 n \mathcal{c} [/math].

Чтобы извлечь элемент, придётся спуститься не больше, чем на глубину дерева. Аналогично для добавления. Поэтому обе операции выполняются за [math] \mathcal{O}(\log n) [/math]

Пример

Рассмотрим поподробнее операции. В худшем случае элементы будут добавляться только в один конец, а извлекаться из другого.

pushFront

Изначально у нас пустой дек. Элементы будем добавлять в левый конец дека и нумеровать согласно порядку добавления. Сначала добавим первый элемент. Он встанет на позицию левого ребёнка первого уровня дека. Теперь попытаемся добавить второй элемент. Позиция левого ребёнка занята, значит, мы объединяем новый элемент со старым и ставим сформированную пару на место левого ребёнка второго дека. Процесс добавления можно представить, как прибавление [math]1[/math] к разряду числа в двоичном представлении: если в разряде [math]1[/math], то подряд идущие за ней единицы обнулятся — элементы объединяются в пары, иначе становятся на место этого разряда.

PushDequeExample.png

popFront

Посмотрим, как работает [math]\mathrm{popFront}[/math] на примере дека, симметричного нашему предыдущему (в рисунке надо понять, что элементы хранятся слева направо: [math]4 ~3 ~2 ~1[/math]).

Запустим первый [math] \mathrm{popFront} [/math]. Он будет рекурсивно вызывать сам себя, пока не дойдёт до последней ветки. Тогда в пару [math] \left \langle temp, ~newDeque \right \rangle [/math] сохранятся две пары элементов и пустой дек высоты [math]0[/math]. Так как [math] temp [/math] не пуст, то в предыдущий рекурсивный вызов вернётся пара: в [math]temp[/math] новый сохранится пара [math]4 ~3[/math], на текущем уровне будет пара [math]2 ~1[/math], а [math] child [/math] будет ссылаться на пустой дек. Потом пара [math]4 ~3[/math] передастся выше, [math]child[/math] сохраняет ссылку на предыдущий дек, и, наконец, [math]4[/math] извлечётся. И так далее. Аналогично добавлению, процесс извлечения элемента можно представить, как вычитание [math]1[/math] из младшего разряда двоичного числа.

PopDequeExample.png

См. также

Источники информации