Дек — различия между версиями
Mutsch (обсуждение | вклад) м (→Реализации) |
м (rollbackEdits.php mass rollback) |
||
| (не показано 20 промежуточных версий 6 участников) | |||
| Строка 1: | Строка 1: | ||
== Определение == | == Определение == | ||
[[Файл:deque1.png|thumb|right|200px|Дек]] | [[Файл:deque1.png|thumb|right|200px|Дек]] | ||
| − | '''Дек''' (от англ. ''deque'' {{---}} double ended queue) {{---}} структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO, поэтому на ней можно реализовать как стек, так и очередь. В первом случае нужно использовать только методы головы или хвоста, во втором {{---}} методы push и pop двух разных концов. Дек можно воспринимать как двустороннюю очередь. Он имеет следующие операции: | + | '''Дек''' (от англ. ''deque'' {{---}} double ended queue) {{---}} структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO, поэтому на ней можно реализовать как [[Стек | стек]], так и [[Очередь | очередь]]. В первом случае нужно использовать только методы головы или хвоста, во втором {{---}} методы push и pop двух разных концов. Дек можно воспринимать как двустороннюю очередь. Он имеет следующие операции: |
* <tex> \mathtt{empty} </tex> {{---}} проверка на наличие элементов, | * <tex> \mathtt{empty} </tex> {{---}} проверка на наличие элементов, | ||
* <tex> \mathtt{pushBack} </tex> (запись в конец) {{---}} операция вставки нового элемента в конец, | * <tex> \mathtt{pushBack} </tex> (запись в конец) {{---}} операция вставки нового элемента в конец, | ||
| Строка 11: | Строка 11: | ||
Дек расходует только <tex>O(n)</tex> памяти, на хранение самих элементов. | Дек расходует только <tex>O(n)</tex> памяти, на хранение самих элементов. | ||
=== Простая реализация === | === Простая реализация === | ||
| − | В данной реализации изначально <tex> \mathtt{ | + | В данной реализации изначально <tex> \mathtt{head = n - 1} </tex> и <tex> \mathtt{tail = n - 1} </tex>. |
Ключевые поля: | Ключевые поля: | ||
* <tex>\mathtt{d[0\dots 2 \times n - 1]}</tex> {{---}} массив, с помощью которого реализуется дек, способный вместить не более <tex>n</tex> элементов, | * <tex>\mathtt{d[0\dots 2 \times n - 1]}</tex> {{---}} массив, с помощью которого реализуется дек, способный вместить не более <tex>n</tex> элементов, | ||
| − | * <tex>\mathtt{ | + | * <tex>\mathtt{head}</tex> {{---}} индекс головы дека, |
| − | * <tex>\mathtt{ | + | * <tex>\mathtt{tail}</tex> {{---}} индекс хвоста. |
| − | Дек состоит из элементов <tex>\mathtt {d[ | + | Дек состоит из элементов <tex>\mathtt {d[head\dots tail - 1]}</tex>. Если происходит максимум <tex>\mathtt {n}</tex> добавлений, то массив длины <tex>\mathtt {2 \times n}</tex> может вместить в себя все добавленные элементы. |
'''boolean''' empty(): | '''boolean''' empty(): | ||
| − | '''return''' | + | '''return''' head == tail |
'''function''' pushBack(x : '''T'''): | '''function''' pushBack(x : '''T'''): | ||
| − | d[ | + | d[tail++] = x |
| − | |||
'''T''' popBack(): | '''T''' popBack(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
'''return''' <span style="color:red">error</span> "underflow" | '''return''' <span style="color:red">error</span> "underflow" | ||
| − | + | '''return''' d[--tail] | |
| − | '''return''' d[ | ||
'''function''' pushFront(x : '''T'''): | '''function''' pushFront(x : '''T'''): | ||
| − | d | + | d[--head] = x |
| − | |||
'''T''' popFront(): | '''T''' popFront(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
'''return''' <span style="color:red">error</span> "underflow" | '''return''' <span style="color:red">error</span> "underflow" | ||
| − | ''' | + | '''return''' d[head++] |
| − | |||
| − | |||
=== Циклический дек на массиве константной длины === | === Циклический дек на массиве константной длины === | ||
| − | Во всех циклических реализациях присвоены следующие значения <tex> \mathtt{head = 0} </tex> и <tex> \mathtt{tail = 0} </tex>. | + | Во всех циклических реализациях изначально присвоены следующие значения <tex> \mathtt{head = 0} </tex> и <tex> \mathtt{tail = 0} </tex>. |
Ключевые поля: | Ключевые поля: | ||
* <tex>\mathtt{d[0\dots n-1]}</tex> {{---}} массив, с помощью которого реализуется дек, способный вместить не более <tex>n</tex> элементов, | * <tex>\mathtt{d[0\dots n-1]}</tex> {{---}} массив, с помощью которого реализуется дек, способный вместить не более <tex>n</tex> элементов, | ||
| − | * <tex>\mathtt{ | + | * <tex>\mathtt{head}</tex> {{---}} индекс головы дека, |
| − | * <tex>\mathtt{ | + | * <tex>\mathtt{tail}</tex> {{---}} индекс хвоста. |
| − | Дек состоит из элементов <tex>\mathtt {d[ | + | Дек состоит из элементов <tex>\mathtt {d[head\dots tail-1]}</tex> или <tex>\mathtt {d[0\dots tail-1]}</tex> и <tex>\mathtt {d[head\dots n-1]}</tex>. Всего он способен вместить не более <tex>n</tex> элементов. В данной реализации учитывается переполнение и правильно обрабатывается изъятие из пустого дека. Недостатком является константная длина массива, хранящего элементы. Все операции выполняются за <tex>O(1)</tex>. |
'''function''' pushBack(x : '''T'''): | '''function''' pushBack(x : '''T'''): | ||
| − | '''if''' ( | + | '''if''' (head == (tail + 1) % n) |
'''return''' <span style="color:red">error</span> "overflow" | '''return''' <span style="color:red">error</span> "overflow" | ||
| − | d[ | + | d[tail] = x |
| − | + | tail = (tail + 1) % n | |
'''T''' popBack(): | '''T''' popBack(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
'''return''' <span style="color:red">error</span> "underflow" | '''return''' <span style="color:red">error</span> "underflow" | ||
| − | + | tail = (tail - 1 + n) % n | |
| − | '''return''' d[ | + | '''return''' d[tail] |
'''function''' pushFront(x : '''T'''): | '''function''' pushFront(x : '''T'''): | ||
| − | '''if''' ( | + | '''if''' (head == (tail + 1) % n) |
'''return''' <span style="color:red">error</span> "overflow" | '''return''' <span style="color:red">error</span> "overflow" | ||
| − | + | head = (head - 1 + n) % n | |
| − | d[ | + | d[head] = x |
'''T''' popFront(): | '''T''' popFront(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
'''return''' <span style="color:red">error</span> "underflow" | '''return''' <span style="color:red">error</span> "underflow" | ||
| − | '''T''' ret = d[ | + | '''T''' ret = d[head] |
| − | + | head = (head + 1) % n | |
'''return''' ret | '''return''' ret | ||
=== Циклический дек на динамическом массиве === | === Циклический дек на динамическом массиве === | ||
Ключевые поля: | Ключевые поля: | ||
| + | * <tex>\mathtt{n}</tex> {{---}} размер массива, | ||
* <tex>\mathtt{d[0\dots n-1]}</tex> {{---}} массив, в котором хранится дек, | * <tex>\mathtt{d[0\dots n-1]}</tex> {{---}} массив, в котором хранится дек, | ||
* <tex>\mathtt{newDeque[0\dots newSize]}</tex> {{---}} временный массив, где хранятся элементы после перекопирования, | * <tex>\mathtt{newDeque[0\dots newSize]}</tex> {{---}} временный массив, где хранятся элементы после перекопирования, | ||
| − | * <tex>\mathtt{ | + | * <tex>\mathtt{head}</tex> {{---}} индекс головы дека, |
| − | * <tex>\mathtt{ | + | * <tex>\mathtt{tail}</tex> {{---}} индекс хвоста. |
| − | |||
| − | Дек состоит из элементов <tex>\mathtt {d[ | + | Дек состоит из элементов <tex>\mathtt {d[head\dots tail-1]}</tex> или <tex>\mathtt {d[0\dots tail-1]}</tex> и <tex>\mathtt {d[head\dots n-1]}</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() | ||
| − | '''if''' | + | '''if''' tail > head |
| − | '''return''' n - | + | '''return''' n - head + tail |
'''else''' | '''else''' | ||
| − | '''return''' | + | '''return''' tail - head |
'''function''' pushBack(x : '''T'''): | '''function''' pushBack(x : '''T'''): | ||
| − | '''if''' ( | + | '''if''' (head == (tail + 1) % n) |
| − | '''T''' newDeque[ | + | '''T''' newDeque[n * 2] |
| − | '''for''' i = 0 '''to''' | + | '''for''' i = 0 '''to''' n - 2 |
| − | newDeque[i] = d[ | + | newDeque[i] = d[head] |
| − | + | head = (head + 1) % n | |
d = newDeque | d = newDeque | ||
| − | + | head = 0 | |
| − | + | tail = n - 1 | |
| − | + | n *= 2 | |
| − | d[ | + | d[tail] = x |
| − | + | tail = (tail + 1) % n | |
'''T''' popBack(): | '''T''' popBack(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
'''return''' <span style="color:red">error</span> "underflow" | '''return''' <span style="color:red">error</span> "underflow" | ||
| − | '''if''' (size() < | + | '''if''' (size() < n / 4) |
| − | '''T''' newDeque[ | + | '''T''' newDeque[n / 2] |
| − | '''for''' i = 0 '''to''' | + | '''int''' dequeSize = size() |
| − | newDeque[i] = d[ | + | '''for''' i = 0 '''to''' dequeSize - 1 |
| − | + | newDeque[i] = d[head] | |
| + | head = (head + 1) % n | ||
d = newDeque | d = newDeque | ||
| − | + | head = 0 | |
| − | + | tail = dequeSize | |
| − | + | n /= 2 | |
| − | + | tail = (tail - 1 + n) % n | |
| − | '''return''' d[ | + | '''return''' d[tail] |
'''function''' pushFront(x : '''T'''): | '''function''' pushFront(x : '''T'''): | ||
| − | '''if''' ( | + | '''if''' (head == (tail + 1) % n) |
| − | '''T''' newDeque[ | + | '''T''' newDeque[n * 2] |
| − | '''for''' i = 0 '''to''' | + | '''for''' i = 0 '''to''' n - 2 |
| − | newDeque[i] = d[ | + | newDeque[i] = d[head] |
| − | + | head = (head + 1) % n | |
d = newDeque | d = newDeque | ||
| − | + | head = 0 | |
| − | + | tail = n - 1 | |
| − | + | n *= 2 | |
| − | + | head = (head - 1 + n) % n | |
| − | d[ | + | d[head] = x |
'''T''' popFront(): | '''T''' popFront(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
'''return''' <span style="color:red">error</span> "underflow" | '''return''' <span style="color:red">error</span> "underflow" | ||
| − | '''if''' (size() < | + | '''if''' (size() < n / 4) |
| − | '''T''' newDeque[ | + | '''T''' newDeque[n / 2] |
| − | '''for''' i = 0 '''to''' | + | '''int''' dequeSize = size() |
| − | newDeque[i] = d[ | + | '''for''' i = 0 '''to''' dequeSize - 1 |
| − | + | newDeque[i] = d[head] | |
| + | head = (head + 1) % n | ||
d = newDeque | d = newDeque | ||
| − | + | head = 0 | |
| − | + | tail = dequeSize | |
| − | + | n /= 2 | |
| − | '''T''' ret = d[ | + | '''T''' ret = d[head] |
| − | + | head = (head + 1) % n | |
'''return''' ret | '''return''' ret | ||
| Строка 186: | Строка 183: | ||
* <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>O(n | + | Храним два [[Стек | стека]] — <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'''): | ||
| Строка 197: | Строка 194: | ||
'''int''' size = rightStack.size() | '''int''' size = rightStack.size() | ||
'''Stack<T>''' local | '''Stack<T>''' local | ||
| − | '''for''' i = 0 '''to''' size / 2 | + | '''for''' i = 0 '''to''' size / 2 |
local.push(rightStack.pop()) | local.push(rightStack.pop()) | ||
'''while''' '''not''' rightStack.empty() | '''while''' '''not''' rightStack.empty() | ||
| Строка 214: | Строка 211: | ||
'''int''' size = leftStack.size() | '''int''' size = leftStack.size() | ||
'''Stack<T>''' local | '''Stack<T>''' local | ||
| − | '''for''' i = 0 '''to''' size / 2 | + | '''for''' i = 0 '''to''' size / 2 |
local.push(leftStack.pop()) | local.push(leftStack.pop()) | ||
'''while''' '''not''' leftStack.empty() | '''while''' '''not''' leftStack.empty() | ||
| Строка 221: | Строка 218: | ||
leftStack.push(local.pop()) | leftStack.push(local.pop()) | ||
'''return''' rightStack.pop() | '''return''' rightStack.pop() | ||
| + | |||
| + | {{Лемма | ||
| + | |statement=Амортизированная стоимость операции в таком деке {{---}} <tex>O(1)</tex>. | ||
| + | |proof=Воспользуемся методом предоплаты для доказательства. Достаточно доказать, что между двумя балансировками происходит достаточно амортизирующих их операций. | ||
| + | |||
| + | Вначале в обоих стеках пусто, поэтому они сбалансированы. Рассмотрим дек после очередной балансировки, будем использовать две монеты для операций <tex>\mathtt{push}</tex> и <tex>\mathtt{pop}</tex> {{---}} одну для самой операции, а другую {{---}} в качестве резерва. | ||
| + | |||
| + | Разберем худший случай: после очередной балансировки происходит удаление всех элементов только из одного стека. В таком случае при удалении кладем одну резервную монету на элемент из другого стека. Тогда учетная стоимость следующей балансировки равна нулю, поскольку на всех элементах дека лежит по монете. | ||
| + | }} | ||
== См. также == | == См. также == | ||
Текущая версия на 19:35, 4 сентября 2022
Определение
Дек (от англ. deque — double ended queue) — структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO, поэтому на ней можно реализовать как стек, так и очередь. В первом случае нужно использовать только методы головы или хвоста, во втором — методы push и pop двух разных концов. Дек можно воспринимать как двустороннюю очередь. Он имеет следующие операции:
- — проверка на наличие элементов,
- (запись в конец) — операция вставки нового элемента в конец,
- (снятие с конца) — операция удаления конечного элемента,
- (запись в начало) — операция вставки нового элемента в начало,
- (снятие с начала) — операция удаления начального элемента.
Реализации
Дек расходует только памяти, на хранение самих элементов.
Простая реализация
В данной реализации изначально и . Ключевые поля:
- — массив, с помощью которого реализуется дек, способный вместить не более элементов,
- — индекс головы дека,
- — индекс хвоста.
Дек состоит из элементов . Если происходит максимум добавлений, то массив длины может вместить в себя все добавленные элементы.
boolean empty(): return head == tail
function pushBack(x : T): d[tail++] = x
T popBack():
if (empty())
return error "underflow"
return d[--tail]
function pushFront(x : T): d[--head] = x
T popFront():
if (empty())
return error "underflow"
return d[head++]
Циклический дек на массиве константной длины
Во всех циклических реализациях изначально присвоены следующие значения и . Ключевые поля:
- — массив, с помощью которого реализуется дек, способный вместить не более элементов,
- — индекс головы дека,
- — индекс хвоста.
Дек состоит из элементов или и . Всего он способен вместить не более элементов. В данной реализации учитывается переполнение и правильно обрабатывается изъятие из пустого дека. Недостатком является константная длина массива, хранящего элементы. Все операции выполняются за .
function pushBack(x : T):
if (head == (tail + 1) % n)
return error "overflow"
d[tail] = x
tail = (tail + 1) % n
T popBack():
if (empty())
return error "underflow"
tail = (tail - 1 + n) % n
return d[tail]
function pushFront(x : T):
if (head == (tail + 1) % n)
return error "overflow"
head = (head - 1 + n) % n
d[head] = x
T popFront():
if (empty())
return error "underflow"
T ret = d[head]
head = (head + 1) % n
return ret
Циклический дек на динамическом массиве
Ключевые поля:
- — размер массива,
- — массив, в котором хранится дек,
- — временный массив, где хранятся элементы после перекопирования,
- — индекс головы дека,
- — индекс хвоста.
Дек состоит из элементов или и . Если реализовывать дек на динамическом массиве, то мы можем избежать ошибки переполнения. При выполнении операций и происходит проверка на переполнение и, если нужно, выделяется большее количество памяти под массив. Также происходит проверка на избыточность памяти, выделенной под дек при выполнении операций и . Если памяти под дек выделено в четыре раза больше размера дека, то массив сокращается в два раза. Для удобства выделим в отдельную функцию получение текущего размера дека.
int size()
if tail > head
return n - head + tail
else
return tail - head
function pushBack(x : T):
if (head == (tail + 1) % n)
T newDeque[n * 2]
for i = 0 to n - 2
newDeque[i] = d[head]
head = (head + 1) % n
d = newDeque
head = 0
tail = n - 1
n *= 2
d[tail] = x
tail = (tail + 1) % n
T popBack():
if (empty())
return error "underflow"
if (size() < n / 4)
T newDeque[n / 2]
int dequeSize = size()
for i = 0 to dequeSize - 1
newDeque[i] = d[head]
head = (head + 1) % n
d = newDeque
head = 0
tail = dequeSize
n /= 2
tail = (tail - 1 + n) % n
return d[tail]
function pushFront(x : T):
if (head == (tail + 1) % n)
T newDeque[n * 2]
for i = 0 to n - 2
newDeque[i] = d[head]
head = (head + 1) % n
d = newDeque
head = 0
tail = n - 1
n *= 2
head = (head - 1 + n) % n
d[head] = x
T popFront():
if (empty())
return error "underflow"
if (size() < n / 4)
T newDeque[n / 2]
int dequeSize = size()
for i = 0 to dequeSize - 1
newDeque[i] = d[head]
head = (head + 1) % n
d = newDeque
head = 0
tail = dequeSize
n /= 2
T ret = d[head]
head = (head + 1) % n
return ret
На списке
Ключевые поля:
-
ListItem(data : T, next : ListItem, prev : ListItem)— конструктор, - — ссылка на хвост,
- — ссылка на голову.
Дек очень просто реализуется на двусвязном списке. Он состоит из элементов . Элементы всегда добавляются либо в , либо в . В данной реализации не учитывается изъятие из пустого дека.
function initialize(): head = ListItem(null, null, null) tail = ListItem(null, null, head) head.next = tail
function pushBack(x : T): head = ListItem(x, head, null) head.next.prev = head
T popBack(): data = head.data head = head.next return data
function pushFront(x : T): tail = ListItem(x, null, tail) tail.prev.next = tail
T popFront(): data = tail.data tail = tail.prev return data
На двух стеках
Ключевые поля:
- — ссылка на хвост,
- — ссылка на голову.
Храним два стека — и . Левый стек используем для операций и , правый — для и . Если мы хотим работать с левым стеком и при этом он оказывается пустым, то достаем нижнюю половину элементов из правого и кладем в левый, воспользовавшись при этом локальным стеком. Аналогично с правым стеком. Худшее время работы — .
function pushBack(x : T): leftStack.push(x)
T popBack():
if not leftStack.empty()
return leftStack.pop()
else
int size = rightStack.size()
Stack<T> local
for i = 0 to size / 2
local.push(rightStack.pop())
while not rightStack.empty()
leftStack.push(rightStack.pop())
while not local.empty()
rightStack.push(local.pop())
return leftStack.pop()
function pushFront(x : T): rightStack.push(x)
T popFront():
if not rightStack.empty()
return rightStack.pop()
else
int size = leftStack.size()
Stack<T> local
for i = 0 to size / 2
local.push(leftStack.pop())
while not leftStack.empty()
rightStack.push(leftStack.pop())
while not local.empty()
leftStack.push(local.pop())
return rightStack.pop()
| Лемма: |
Амортизированная стоимость операции в таком деке — . |
| Доказательство: |
|
Воспользуемся методом предоплаты для доказательства. Достаточно доказать, что между двумя балансировками происходит достаточно амортизирующих их операций. Вначале в обоих стеках пусто, поэтому они сбалансированы. Рассмотрим дек после очередной балансировки, будем использовать две монеты для операций и — одну для самой операции, а другую — в качестве резерва. Разберем худший случай: после очередной балансировки происходит удаление всех элементов только из одного стека. В таком случае при удалении кладем одну резервную монету на элемент из другого стека. Тогда учетная стоимость следующей балансировки равна нулю, поскольку на всех элементах дека лежит по монете. |