Дек — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 1: Строка 1:
 
== Определение ==
 
== Определение ==
 
[[Файл:deque1.png|thumb|right|200px|Дек]]
 
[[Файл:deque1.png|thumb|right|200px|Дек]]
'''Дек''' (от англ. ''deque'' {{---}} double ended queue) {{---}} структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Дек можно воспринимать как двустороннюю очередь или двусторонний стек. Он имеет следующие операции:
+
'''Дек''' (от англ. ''deque'' {{---}} double ended queue) {{---}} структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO. Дек можно воспринимать как двустороннюю очередь Он имеет следующие операции:
 
* <tex> \mathtt{empty} </tex> {{---}} проверка на наличие элементов,
 
* <tex> \mathtt{empty} </tex> {{---}} проверка на наличие элементов,
 
* <tex> \mathtt{pushBack} </tex> (запись в конец) {{---}} операция вставки нового элемента в конец,
 
* <tex> \mathtt{pushBack} </tex> (запись в конец) {{---}} операция вставки нового элемента в конец,
Строка 8: Строка 8:
 
* <tex> \mathtt{popFront} </tex> (снятие с начала) {{---}} операция удаления начального элемента.
 
* <tex> \mathtt{popFront} </tex> (снятие с начала) {{---}} операция удаления начального элемента.
  
==Реализации==
+
== Реализации ==
 
Дек расходует только <tex>O(n)</tex> памяти, на хранение самих элементов.
 
Дек расходует только <tex>O(n)</tex> памяти, на хранение самих элементов.
 
=== На массиве ===
 
=== На массиве ===
Строка 14: Строка 14:
 
* <tex>\mathtt{d[1\dots n]}</tex> {{---}} массив, с помощью которого реализуется дек, способный вместить не более <tex>n</tex> элементов,
 
* <tex>\mathtt{d[1\dots n]}</tex> {{---}} массив, с помощью которого реализуется дек, способный вместить не более <tex>n</tex> элементов,
 
* <tex>\mathtt{d.head}</tex> {{---}} индекс головы дека,
 
* <tex>\mathtt{d.head}</tex> {{---}} индекс головы дека,
* <tex>\mathtt{d.tail}</tex> {{---}} индекс хвоста дека.
+
* <tex>\mathtt{d.tail}</tex> {{---}} индекс хвоста.
  
Дек состоит из элементов <tex>\mathtt {d[d.tail\dots d.head]}</tex>. Всего дек способен вместить не более <tex>n</tex> элементов, поэтому при переполнении приходится
+
Дек состоит из элементов <tex>\mathtt {d[d.tail\dots d.head]}</tex>. Всего он способен вместить не более <tex>n</tex> элементов. В данной реализации учитывается переполнение и правильно обрабатывается изъятие из пустого дека. Недостатком является константная длина массива, хранящего элементы. Все операции выполняются за <tex>O(1)</tex>.
перевыделять память и копировать все элементы.
 
  
 
  '''boolean''' empty():
 
  '''boolean''' empty():
Строка 46: Строка 45:
 
   '''return''' d[d.head]
 
   '''return''' d[d.head]
  
Все операции выполняются за <tex>O(1)</tex>.
 
 
=== На саморасширяющемся массиве ===
 
=== На саморасширяющемся массиве ===
 
Ключевые поля:
 
Ключевые поля:
Строка 52: Строка 50:
 
* <tex>\mathtt{newDeque[1\dots newSize]}</tex> {{---}} временный массив, где хранятся элементы после перекопирования,
 
* <tex>\mathtt{newDeque[1\dots newSize]}</tex> {{---}} временный массив, где хранятся элементы после перекопирования,
 
* <tex>\mathtt{d.head}</tex> {{---}} индекс головы дека,
 
* <tex>\mathtt{d.head}</tex> {{---}} индекс головы дека,
* <tex>\mathtt{d.tail}</tex> {{---}} индекс хвоста дека,
+
* <tex>\mathtt{d.tail}</tex> {{---}} индекс хвоста,
 
* <tex>\mathtt{capacity}</tex> {{---}} размер массива.
 
* <tex>\mathtt{capacity}</tex> {{---}} размер массива.
  
Если реализовывать дек на динамическом массиве, то мы можем избежать ошибки переполнения. При выполнении операций <tex>\mathtt{pushBack}/tex> и <tex>\mathtt{pushFront}</tex> происходит проверка на переполнение и, если нужно, выделяется большее количество памяти под массив. Также проверяем, не нужно ли нам уменьшить размер массива при выполнении операций <tex>\mathtt{popBack}</tex> и <tex>\mathtt{popFront}</tex>. Для удобства выделим в отдельную функцию <tex>\mathtt{size}</tex> получение размера дека.
+
Если реализовывать дек на динамическом массиве, то мы можем избежать ошибки переполнения. При выполнении операций <tex>\mathtt{pushBack}/tex> и <tex>\mathtt{pushFront}</tex> происходит проверка на переполнение и, если нужно, выделяется большее количество памяти под массив. Также происходит проверка на избыточность памяти, выделенной под дек при выполнении операций <tex>\mathtt{popBack}</tex> и <tex>\mathtt{popFront}</tex>. Если памяти слишком много, то массив сокращается. Для удобства выделим в отдельную функцию <tex>\mathtt{size}</tex> получение текущего размера дека.
 
   
 
   
 
  '''int''' size()
 
  '''int''' size()
Строка 122: Строка 120:
 
* <tex>\mathtt{head}</tex> {{---}} ссылка на голову.
 
* <tex>\mathtt{head}</tex> {{---}} ссылка на голову.
  
Дек очень просто реализуется на двусвязном списке. Элементы всегда добавляются либо в <tex>\mathtt{tail.prev}</tex>, либо в <tex>\mathtt{head.next}</tex>. В данной реализации не учитывается пустой дек.
+
Дек очень просто реализуется на двусвязном списке. Элементы всегда добавляются либо в <tex>\mathtt{tail.prev}</tex>, либо в <tex>\mathtt{head.next}</tex>. В данной реализации не учитывается изъятие из пустого дека.
  
 
  '''function''' pushBack(x : '''T'''):
 
  '''function''' pushBack(x : '''T'''):
Строка 147: Строка 145:
 
* <tex>\mathtt{rightStack}</tex> {{---}} ссылка на голову.
 
* <tex>\mathtt{rightStack}</tex> {{---}} ссылка на голову.
  
Храним два стека - <tex>\mathtt{leftStack}</tex> и <tex>\mathtt{rightStack}</tex>. Левый стек используем для операций <tex>\mathtt{popBack}</tex> и <tex>\mathtt{pushBack}/tex>, правый - для <tex>\mathtt{popFront}</tex> и <tex>\mathtt{pushFront}</tex>.
+
Храним два стека - <tex>\mathtt{leftStack}</tex> и <tex>\mathtt{rightStack}</tex>. Левый стек используем для операций <tex>\mathtt{popBack}</tex> и <tex>\mathtt{pushBack]</tex>, правый - для <tex>\mathtt{popFront}</tex> и <tex>\mathtt{pushFront}</tex>. Если мы хотим работать с левым стеком и при этом он оказывается пустым, то по очереди достаем все элементы из правого и кладем в левый. Аналогично с правым стеком. Худшее время работы каждой операции - <tex>O(n)</tex>.
  
 
  '''function''' pushBack(x : '''T'''):
 
  '''function''' pushBack(x : '''T'''):
Строка 176: Строка 174:
 
* [[Персистентный дек]]
 
* [[Персистентный дек]]
  
==Источники информации==
+
== Источники информации ==
 +
* [[wikipedia:ru:Двусвязная_очередь|Википедия {{---}} Дек (программирование)]]
 +
 
 +
[[Категория: Дискретная математика и алгоритмы]]

Версия 08:00, 6 января 2016

Определение

Дек

Дек (от англ. deque — double ended queue) — структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO. Дек можно воспринимать как двустороннюю очередь Он имеет следующие операции:

  • [math] \mathtt{empty} [/math] — проверка на наличие элементов,
  • [math] \mathtt{pushBack} [/math] (запись в конец) — операция вставки нового элемента в конец,
  • [math] \mathtt{popBack} [/math] (снятие с конца) — операция удаления конечного элемента,
  • [math] \mathtt{pushFront} [/math] (запись в начало) — операция вставки нового элемента в начало,
  • [math] \mathtt{popFront} [/math] (снятие с начала) — операция удаления начального элемента.

Реализации

Дек расходует только [math]O(n)[/math] памяти, на хранение самих элементов.

На массиве

Ключевые поля:

  • [math]\mathtt{d[1\dots n]}[/math] — массив, с помощью которого реализуется дек, способный вместить не более [math]n[/math] элементов,
  • [math]\mathtt{d.head}[/math] — индекс головы дека,
  • [math]\mathtt{d.tail}[/math] — индекс хвоста.

Дек состоит из элементов [math]\mathtt {d[d.tail\dots d.head]}[/math]. Всего он способен вместить не более [math]n[/math] элементов. В данной реализации учитывается переполнение и правильно обрабатывается изъятие из пустого дека. Недостатком является константная длина массива, хранящего элементы. Все операции выполняются за [math]O(1)[/math].

boolean empty():
  return d.head % n + 1 == d.tail
function pushBack(x : T):
  if (d.head == d.tail)
    return error "overflow"
  d[d.tail] = x
  d.tail = (d.tail - 2 + n) % n + 1
T popBack():
  if (empty()) 
    return error "underflow" 
  d.tail = d.tail % n + 1
  return d[d.tail]
function pushFront(x : T):
  if (d.head == d.tail)
    return error "overflow"
  d[d.head] = x
  d.head = d.head % n + 1
T popFront():
  if (empty()) 
    return error "underflow" 
  d.head = (d.head - 2 + n) % n + 1
  return d[d.head]

На саморасширяющемся массиве

Ключевые поля:

  • [math]\mathtt{d[1\dots n]}[/math] — массив, в котором хранится дек,
  • [math]\mathtt{newDeque[1\dots newSize]}[/math] — временный массив, где хранятся элементы после перекопирования,
  • [math]\mathtt{d.head}[/math] — индекс головы дека,
  • [math]\mathtt{d.tail}[/math] — индекс хвоста,
  • [math]\mathtt{capacity}[/math] — размер массива.

Если реализовывать дек на динамическом массиве, то мы можем избежать ошибки переполнения. При выполнении операций [math]\mathtt{pushBack}/tex\gt и \lt tex\gt \mathtt{pushFront}[/math] происходит проверка на переполнение и, если нужно, выделяется большее количество памяти под массив. Также происходит проверка на избыточность памяти, выделенной под дек при выполнении операций [math]\mathtt{popBack}[/math] и [math]\mathtt{popFront}[/math]. Если памяти слишком много, то массив сокращается. Для удобства выделим в отдельную функцию [math]\mathtt{size}[/math] получение текущего размера дека.

int size()
  if d.tail > d.head
    return n - d.tail + d.head - 1
  else
    return d.head - d.tail - 1
function pushBack(x : T):
  if (d.head == d.tail)
    T newDeque[capacity * 2]
    for i = 1 to capacity - 1
      newDeque[i] = d[d.tail + 1]
      d.tail = d.tail % n + 1
    d = newDeque
    d.tail = capacity * 2
    d.head = capacity - 1
    capacity = capacity * 2
  d[d.tail] = x
  d.tail = (d.tail - 2 + n) % n + 1
T popBack():
  if (empty()) 
    return error "underflow"
  if (size() < capacity / 4)
    T newDeque[capacity / 2]
    for i = 1 to size()
      newDeque[i] = d[d.tail + 1]
      d.tail = d.tail % n + 1
    d = newDeque
    d.tail = capacity / 2
    d.head = size() + 1
  d.tail = d.tail % n + 1
  return d[d.tail]
function pushFront(x : T):
  if (d.head == d.tail)
    T newDeque[capacity * 2]
    for i = 1 to capacity - 1
      newDeque[i] = d[d.tail + 1]
      d.tail = d.tail % n + 1
    d = newDeque
    d.tail = capacity * 2
    d.head = capacity - 1
  d[d.head] = x
  d.head = d.head % n + 1
T popFront():
  if (empty()) 
    return error "underflow" 
  if (size() < capacity / 4)
    T newDeque[capacity / 2]
    for i = 1 to size()
      newDeque[i] = d[d.tail + 1]
      d.tail = d.tail % n + 1
    d = newDeque
    d.tail = capacity / 2
    d.head = size() + 1
  d.head = (d.head - 2 + n) % n + 1
  return d[d.head]

На списке

Ключевые поля:

  • ListItem(data : T, next : ListItem, prev : ListItem) — конструктор,
  • [math]\mathtt{tail}[/math] — ссылка на хвост,
  • [math]\mathtt{head}[/math] — ссылка на голову.

Дек очень просто реализуется на двусвязном списке. Элементы всегда добавляются либо в [math]\mathtt{tail.prev}[/math], либо в [math]\mathtt{head.next}[/math]. В данной реализации не учитывается изъятие из пустого дека.

function pushBack(x : T):
  tail = ListItem(x, tail, null)
  tail.next.prev = tail
T popBack():
  data = tail.data
  tail = tail.next
  return data
function pushFront(x : T):
  head = ListItem(x, null, front)
  head.prev.next = head
T popFront():
  data = head.data
  head = head.prev
  return data

На двух стеках

Ключевые поля:

  • [math]\mathtt{leftStack}[/math] — ссылка на хвост,
  • [math]\mathtt{rightStack}[/math] — ссылка на голову.

Храним два стека - [math]\mathtt{leftStack}[/math] и [math]\mathtt{rightStack}[/math]. Левый стек используем для операций [math]\mathtt{popBack}[/math] и [math]\mathtt{pushBack][/math], правый - для [math]\mathtt{popFront}[/math] и [math]\mathtt{pushFront}[/math]. Если мы хотим работать с левым стеком и при этом он оказывается пустым, то по очереди достаем все элементы из правого и кладем в левый. Аналогично с правым стеком. Худшее время работы каждой операции - [math]O(n)[/math].

function pushBack(x : T):
  leftStack.push(x)
T popBack():
  if not leftStack.empty()
    return leftStack.pop() 
  else
    while not rightStack.empty()
      leftStack.push(rightStack.pop())
    return leftStack.pop()
function pushFront(x : T):
  rightStack.push(x)
T popFront():
  if not rightStack.empty()
    return rightStack.pop() 
  else
    while not leftStack.empty()
      rightStack.push(leftStack.pop())
    return rightStack.pop()

См. также

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