Дек — различия между версиями
Mutsch (обсуждение | вклад) м |
Mutsch (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
== Определение == | == Определение == | ||
[[Файл:deque1.png|thumb|right|200px|Дек]] | [[Файл:deque1.png|thumb|right|200px|Дек]] | ||
− | '''Дек''' (от англ. ''deque'' {{---}} double ended queue) {{---}} структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO. Дек можно воспринимать как двустороннюю очередь Он имеет следующие операции: | + | '''Дек''' (от англ. ''deque'' {{---}} double ended queue) {{---}} структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO, поэтому на ней можно реализовать как стек, так и очередь. В первом случае нужно использовать только методы головы или хвоста, во втором - методы push и pop двух разных концов. Дек можно воспринимать как двустороннюю очередь. Он имеет следующие операции: |
* <tex> \mathtt{empty} </tex> {{---}} проверка на наличие элементов, | * <tex> \mathtt{empty} </tex> {{---}} проверка на наличие элементов, | ||
* <tex> \mathtt{pushBack} </tex> (запись в конец) {{---}} операция вставки нового элемента в конец, | * <tex> \mathtt{pushBack} </tex> (запись в конец) {{---}} операция вставки нового элемента в конец, | ||
Строка 10: | Строка 10: | ||
== Реализации == | == Реализации == | ||
Дек расходует только <tex>O(n)</tex> памяти, на хранение самих элементов. | Дек расходует только <tex>O(n)</tex> памяти, на хранение самих элементов. | ||
− | === | + | Изначально переменные <tex> \mathtt{head} </tex> и <tex> \mathtt{tail} </tex> должны различаться на единицу, причем <tex> \mathtt{head = tail - 1} </tex>. |
+ | === Циклический дек на массиве константной длины === | ||
Ключевые поля: | Ключевые поля: | ||
− | * <tex>\mathtt{d[ | + | * <tex>\mathtt{d[0\dots n-1]}</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. | + | Дек состоит из элементов <tex>\mathtt {d[d.head\dots d.tail]}</tex>. Всего он способен вместить не более <tex>n</tex> элементов. В данной реализации учитывается переполнение и правильно обрабатывается изъятие из пустого дека. Недостатком является константная длина массива, хранящего элементы. Все операции выполняются за <tex>O(1)</tex>. |
'''boolean''' empty(): | '''boolean''' empty(): | ||
− | '''return''' d.head % n | + | '''return''' (d.head + 1) % n == d.tail |
'''function''' pushBack(x : '''T'''): | '''function''' pushBack(x : '''T'''): | ||
'''if''' (d.head == d.tail) | '''if''' (d.head == d.tail) | ||
− | '''return''' error "overflow" | + | '''return''' <span style="color:red">error</span> "overflow" |
d[d.tail] = x | d[d.tail] = x | ||
− | d.tail = (d.tail | + | d.tail = (d.tail + 1) % n |
'''T''' popBack(): | '''T''' popBack(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
− | '''return''' error "underflow" | + | '''return''' <span style="color:red">error</span> "underflow" |
− | d.tail = d.tail % n | + | d.tail = (d.tail - 1 + n) % n |
'''return''' d[d.tail] | '''return''' d[d.tail] | ||
'''function''' pushFront(x : '''T'''): | '''function''' pushFront(x : '''T'''): | ||
'''if''' (d.head == d.tail) | '''if''' (d.head == d.tail) | ||
− | '''return''' error "overflow" | + | '''return''' <span style="color:red">error</span> "overflow" |
d[d.head] = x | d[d.head] = x | ||
− | d.head = d.head % n | + | d.head = (d.head - 1 + n) % n |
'''T''' popFront(): | '''T''' popFront(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
− | '''return''' error "underflow" | + | '''return''' <span style="color:red">error</span> "underflow" |
− | d.head = (d.head | + | d.head = (d.head + 1) % n |
'''return''' d[d.head] | '''return''' d[d.head] | ||
− | === | + | === Циклический дек на динамическом массиве === |
Ключевые поля: | Ключевые поля: | ||
− | * <tex>\mathtt{d[ | + | * <tex>\mathtt{d[0\dots n-1]}</tex> {{---}} массив, в котором хранится дек, |
− | * <tex>\mathtt{newDeque[ | + | * <tex>\mathtt{newDeque[0\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{pushBack}</tex> и <tex>\mathtt{pushFront}</tex> происходит проверка на переполнение и, если нужно, выделяется большее количество памяти под массив. Также происходит проверка на избыточность памяти, выделенной под дек при выполнении операций <tex>\mathtt{popBack}</tex> и <tex>\mathtt{popFront}</tex>. Если памяти под дек выделено в четыре раза больше размера дека, то массив сокращается в два раза. Для удобства выделим в отдельную функцию <tex>\mathtt{size}</tex> получение текущего размера дека. |
'''int''' size() | '''int''' size() | ||
'''if''' d.tail > d.head | '''if''' d.tail > d.head | ||
− | '''return''' n - d. | + | '''return''' n - d.head + d.tail - 1 |
'''else''' | '''else''' | ||
− | '''return''' d. | + | '''return''' d.tail - d.head - 1 |
'''function''' pushBack(x : '''T'''): | '''function''' pushBack(x : '''T'''): | ||
Строка 65: | Строка 66: | ||
'''T''' newDeque[capacity * 2] | '''T''' newDeque[capacity * 2] | ||
'''for''' i = 1 '''to''' capacity - 1 | '''for''' i = 1 '''to''' capacity - 1 | ||
− | newDeque[i] = d[d. | + | newDeque[i] = d[d.head + 1] |
− | d. | + | d.head = (d.head + 1) % n |
d = newDeque | d = newDeque | ||
− | d.tail = capacity | + | d.tail = capacity - 1 |
− | d.head = capacity | + | d.head = capacity * 2 |
capacity = capacity * 2 | capacity = capacity * 2 | ||
d[d.tail] = x | d[d.tail] = x | ||
− | d.tail = (d.tail | + | d.tail = (d.tail + 1) % n |
'''T''' popBack(): | '''T''' popBack(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
− | '''return''' error "underflow" | + | '''return''' <span style="color:red">error</span> "underflow" |
'''if''' (size() < capacity / 4) | '''if''' (size() < capacity / 4) | ||
'''T''' newDeque[capacity / 2] | '''T''' newDeque[capacity / 2] | ||
'''for''' i = 1 '''to''' size() | '''for''' i = 1 '''to''' size() | ||
− | newDeque[i] = d[d. | + | newDeque[i] = d[d.head + 1] |
− | d. | + | d.head = (d.head + 1) % n |
d = newDeque | d = newDeque | ||
− | d. | + | d.head = capacity / 2 - 1 |
− | d. | + | d.tail = size() + 1 |
− | d.tail = d.tail % n | + | d.tail = (d.tail - 1 + n) % n |
'''return''' d[d.tail] | '''return''' d[d.tail] | ||
Строка 92: | Строка 93: | ||
'''T''' newDeque[capacity * 2] | '''T''' newDeque[capacity * 2] | ||
'''for''' i = 1 '''to''' capacity - 1 | '''for''' i = 1 '''to''' capacity - 1 | ||
− | newDeque[i] = d[d. | + | newDeque[i] = d[d.head + 1] |
− | d. | + | d.head = (d.head + 1) % n |
d = newDeque | d = newDeque | ||
− | d.tail = capacity | + | d.tail = capacity - 1 |
− | d.head = capacity | + | d.head = capacity * 2 |
d[d.head] = x | d[d.head] = x | ||
− | d.head = d.head % n | + | d.head = (d.head - 1 + n) % n |
'''T''' popFront(): | '''T''' popFront(): | ||
'''if''' (empty()) | '''if''' (empty()) | ||
− | '''return''' error "underflow" | + | '''return''' <span style="color:red">error</span> "underflow" |
'''if''' (size() < capacity / 4) | '''if''' (size() < capacity / 4) | ||
'''T''' newDeque[capacity / 2] | '''T''' newDeque[capacity / 2] | ||
'''for''' i = 1 '''to''' size() | '''for''' i = 1 '''to''' size() | ||
− | newDeque[i] = d[d. | + | newDeque[i] = d[d.head + 1] |
− | d. | + | d.head = (d.head + 1) % n |
d = newDeque | d = newDeque | ||
− | d. | + | d.head = capacity / 2 - 1 |
− | d. | + | d.tail = size() + 1 |
− | d.head = (d.head | + | d.head = (d.head + 1) % n |
'''return''' d[d.head] | '''return''' d[d.head] | ||
Строка 123: | Строка 124: | ||
'''function''' pushBack(x : '''T'''): | '''function''' pushBack(x : '''T'''): | ||
− | + | head = ListItem(x, head, ''null'') | |
− | + | head.next.prev = head | |
'''T''' popBack(): | '''T''' popBack(): | ||
− | data = | + | data = head.data |
− | + | head = head.next | |
'''return''' data | '''return''' data | ||
'''function''' pushFront(x : '''T'''): | '''function''' pushFront(x : '''T'''): | ||
− | + | tail = ListItem(x, ''null'', tail) | |
− | + | tail.prev.next = tail | |
'''T''' popFront(): | '''T''' popFront(): | ||
− | data = | + | data = tail.data |
− | + | tail = tail.prev | |
'''return''' data | '''return''' data | ||
Версия 01:49, 7 января 2016
Содержание
Определение
Дек (от англ. deque — double ended queue) — структура данных, представляющая из себя список элементов, в которой добавление новых элементов и удаление существующих производится с обоих концов. Эта структура поддерживает как FIFO, так и LIFO, поэтому на ней можно реализовать как стек, так и очередь. В первом случае нужно использовать только методы головы или хвоста, во втором - методы push и pop двух разных концов. Дек можно воспринимать как двустороннюю очередь. Он имеет следующие операции:
- — проверка на наличие элементов,
- (запись в конец) — операция вставки нового элемента в конец,
- (снятие с конца) — операция удаления конечного элемента,
- (запись в начало) — операция вставки нового элемента в начало,
- (снятие с начала) — операция удаления начального элемента.
Реализации
Дек расходует только
памяти, на хранение самих элементов. Изначально переменные и должны различаться на единицу, причем .Циклический дек на массиве константной длины
Ключевые поля:
- — массив, с помощью которого реализуется дек, способный вместить не более элементов,
- — индекс головы дека,
- — индекс хвоста.
Дек состоит из элементов
. Всего он способен вместить не более элементов. В данной реализации учитывается переполнение и правильно обрабатывается изъятие из пустого дека. Недостатком является константная длина массива, хранящего элементы. Все операции выполняются за .boolean empty(): return (d.head + 1) % n == d.tail
function pushBack(x : T):
if (d.head == d.tail)
return error "overflow"
d[d.tail] = x
d.tail = (d.tail + 1) % n
T popBack():
if (empty())
return error "underflow"
d.tail = (d.tail - 1 + n) % n
return d[d.tail]
function pushFront(x : T):
if (d.head == d.tail)
return error "overflow"
d[d.head] = x
d.head = (d.head - 1 + n) % n
T popFront():
if (empty())
return error "underflow"
d.head = (d.head + 1) % n
return d[d.head]
Циклический дек на динамическом массиве
Ключевые поля:
- — массив, в котором хранится дек,
- — временный массив, где хранятся элементы после перекопирования,
- — индекс головы дека,
- — индекс хвоста,
- — размер массива.
Если реализовывать дек на динамическом массиве, то мы можем избежать ошибки переполнения. При выполнении операций
и происходит проверка на переполнение и, если нужно, выделяется большее количество памяти под массив. Также происходит проверка на избыточность памяти, выделенной под дек при выполнении операций и . Если памяти под дек выделено в четыре раза больше размера дека, то массив сокращается в два раза. Для удобства выделим в отдельную функцию получение текущего размера дека.int size() if d.tail > d.head return n - d.head + d.tail - 1 else return d.tail - d.head - 1
function pushBack(x : T): if (d.head == d.tail) T newDeque[capacity * 2] for i = 1 to capacity - 1 newDeque[i] = d[d.head + 1] d.head = (d.head + 1) % n d = newDeque d.tail = capacity - 1 d.head = capacity * 2 capacity = capacity * 2 d[d.tail] = x d.tail = (d.tail + 1) % n
T popBack():
if (empty())
return error "underflow"
if (size() < capacity / 4)
T newDeque[capacity / 2]
for i = 1 to size()
newDeque[i] = d[d.head + 1]
d.head = (d.head + 1) % n
d = newDeque
d.head = capacity / 2 - 1
d.tail = size() + 1
d.tail = (d.tail - 1 + n) % n
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.head + 1] d.head = (d.head + 1) % n d = newDeque d.tail = capacity - 1 d.head = capacity * 2 d[d.head] = x d.head = (d.head - 1 + n) % n
T popFront():
if (empty())
return error "underflow"
if (size() < capacity / 4)
T newDeque[capacity / 2]
for i = 1 to size()
newDeque[i] = d[d.head + 1]
d.head = (d.head + 1) % n
d = newDeque
d.head = capacity / 2 - 1
d.tail = size() + 1
d.head = (d.head + 1) % n
return d[d.head]
На списке
Ключевые поля:
-
ListItem(data : T, next : ListItem, prev : ListItem)
— конструктор, - — ссылка на хвост,
- — ссылка на голову.
Дек очень просто реализуется на двусвязном списке. Элементы всегда добавляются либо в
, либо в . В данной реализации не учитывается изъятие из пустого дека.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 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()