Изменения

Перейти к: навигация, поиск

Персистентная очередь

17 108 байт добавлено, 23:31, 5 сентября 2019
м
Правка орфографии
После того, как мы получили [[Очередь#Реализация на шести стеках|'''Персистентная очередь в реальном времени]] с <tex>O''' (1англ. ''persistent queue'')=6</tex> обычными {{---}} это [[СтекОчередь|стекамиочередь]], ее можно легко превратить в персистентную, сделав все стеки реализующая [[Персистентный стекПерсистентные структуры данных|персистентнымиперсистентность]], но на самом деле то есть позволяющая получить доступ ко всем своим предыдущим версиям. Как будет показано далее, можно реализовать функциональную персистентность позволяет , то есть каждая ячейка памяти в такой структуре будет инициализирована один раз и в дальнейшем не создавать явной копии стека, так что достаточно всего пяти стековизменится.
== Эффективная реализация Основная идея ==
Будем отталкиваться от реализации Для создания персистентной очереди очень удобно пользоваться ее реализацией на [[Стек|стеках]], поскольку стеки легко сделать [[Персистентный стек|персистентными]], причем в этом случае мы добьемся функциональной персистентности. [[Очередь#Реализация на двух стеках|Реализация на двух стеках]]. Пусть у нас есть стек <tex>L</tex> не подходит для операций <tex>push</tex>этого, стек так как в худшем случае требует <tex>R</tex> для операций <tex>pop</tex>.К моменту опустошения стека <tex>R</tex> нам нужно получить стек <tex>R'O(n)</tex>времени, содержащий все элементы стека а значит и <tex>LO(n)</tex> памяти на операцию в правильном для извлечения порядкеслучае персистентности. Этого можно добиться, если переместить все элементы стека <tex>L</tex>Покажем сначала, как создать очередь в стек реальном времени с <tex>R'O(1)</tex>, назовем такой процесс перекопированиемвремени на операцию, а очередь будет в это время находится затем превратим ее в режиме перекопирования (''recopy mode''). Режим перекопирования активируется, когда во время очередной операции мы уже можем не успеть переместить нужные элементы, а именно <tex>L.size>R.size</tex>. Состояние очереди будет фиксировать логическая переменная <tex>recopy</tex>персистентную.
Теперь рассмотрим как обрабатывать операции во время перекопирования. В режиме перекопирования операция <tex>empty=false</tex>, так как мы не можем извлекать элементы, находившиеся в не пустом стеке <tex>L</tex>, значит очередь всегда не пуста. Договоримся также, что операция <tex>empty</tex> не меняет внутреннюю структуру = Реализация очереди и не создает новой версии очереди.на шести стеках ==
Поскольку стек <tex>L</tex> во время перемещения содержимого теряет свою структуруОдним из минусов реализации на двух стеках является то, что в худшем случае мы не сможем положить туда элементы, пришедшие с тратим <tex>pushO(n)</tex>времени на операцию. Для таких Если распределить время, необходимое для перемещения элементов заведем специальный стек <tex>L'</tex> из одного стека в который будем складывать поступающие элементыдругой, а после перекопирования обменяем его ролями по операциям, мы получим очередь без худших случаев с <tex>LO(1)</tex>истинного времени на операцию.
Этот алгоритм почти работает, но Сначала будем действовать аналогично случаю с двумя стеками. Пусть у нас есть проблема: никто не гарантирует, что стек <tex>L</tex> для операций <tex>\mathtt{push}</tex> и стек <tex>R</tex> опустошится за время перекопирования, то есть мы получим в итоге два стека с выходными данными, а значит во время следующего перекопирования у нас может не быть свободного стека (например, если все операции были для операций <tex>push\mathtt{pop}</tex>). Избавимся от этой проблемы следующим образом: мы принудительно извлечем все элементы К моменту опустошения стека <tex>R</tex> во вспомогательный нам нужно успеть получить стек <tex>TR'</tex>, затем переместим все содержащий текущие элементы стека <tex>L</tex> в стек правильном для извлечения порядке. Перекопирование (''recopy mode'') начнется, когда появится опасность того, что мы не сможем за оставшиеся <tex>R.size</tex> операций <tex>\mathtt{pop}</tex> со стеком <tex>R</tex>, затем обратно переместим все элементы перекопировать стек <tex>TL</tex> в новый стек <tex>R'</tex>. Легко показатьОчевидно, что такая последовательность действий приведет к правильному для извлечения порядку элементов в это ситуация <tex>L.size>R.size</tex>, пусть такое состояние отражает специальная переменная логического типа <tex>\mathtt{recopy}</tex>.
Теперь разберемся с операциями Понятно, что во время перекопирования могут поступить операции <tex>pop\mathtt{push}</tex>. Теперь у нас теряет свою структуру , а стек <tex>RL</tex>в это время потеряет свою структуру, сложить элементы туда мы уже не сможем, значит нужна его копия для извлечения элементов. Без персистентности нам бы пришлось создавать такую копию параллельно с самим стеком нужно завести еще один стек <tex>RL'</tex>, но теперь в который мы можем просто записать в и будем складывать новые элементы. После окончания перекопирования мы поменяем ролями <tex>RL,L'</tex> последнюю версию стека и <tex>R</tex> и дальше извлекать элементы уже из нее. Чтобы учесть извлеченные из копии элементы, будем использовать переменную <tex>toCopy=R'.size</tex>, будем уменьшать ее на <tex>1</tex> при каждом извлечении из <tex>T</tex> и операциях <tex>pop</tex>. Так как извлеченные элементы будут нарастать со дна стека <tex>T</tex>первый взгляд, мы никогда не извлечем некорректный элемент, если <tex>toCopy>0</tex>все станет хорошо.
Теперь проанализируем наш Однако, если реализовать этот алгоритм. Пусть в стеке , мы получим неприятную вещь: старый стек <tex>R</tex> на начало перекопирования содержатся <tex>n</tex> элементовможет и не опустошиться за это время, то есть мы получили два стека с выходными данными, а значит, возможен случай (например, тогда в стеке <tex>R</tex> находится <tex>n+1</tex> элемент. Мы можем корректно обработать если все поступающие операции {{---}} <tex>\mathtt{push}</tex>), а также когда при следующем перекопировании у нас не будет свободного стека для копирования туда элементов <tex>n</tex> операций <tex>pop</tex>, операции <tex>emptyL</tex> мы не учитываем. Тогда Для преодоления этой проблемы мы должны завершить перекопирование не больше, чем за <tex>n+1</tex> операцию. Всего в режиме перекопирования нужно:# Переместить содержимое принудительно будем извлекать все элементы из стека <tex>R</tex> в во вспомогательный стек <tex>TS</tex>, <tex>n</tex>времени, <tex>n</tex> памяти.# Переместить содержимое затем копировать элементы из стека <tex>L</tex> в <tex>R</tex>, <tex>n+1</tex> времени, <tex>n+1</tex> памяти.# Переместить первые <tex>toCopy</tex> элементов а затем обратно копировать элементы из стека <tex>TS</tex> в стек <tex>R</tex>. Легко показать, остальные выкинуть, что приведенный алгоритм как раз получает на выходе в <tex>nR</tex> времени, <tex>n</tex> памяти.# Поменять ролями стеки все элементы стеков <tex>L, L'R</tex>, <tex>1</tex> временив правильном порядке.
Таким образомНо этого еще недостаточно. Если мы принудительно извлекаем элементы из стека <tex>R</tex>, мы получили появляются следующие проблемы:# Что вернуть при операции <tex>3 \cdot n + 2mathtt{pop}</tex>? Для этого заведем себе стек <tex>Rc</tex> времени {{---}} копию стека <tex>R</tex>, из которого мы и будем извлекать требуемые элементы.# Как поддерживать корректность такой копии? Поскольку этот стек нужен только для перекопирования, а во время него он занят, нужна запасная копия <tex>3 \cdot n + 1Rc'</tex> для копирования всех элементов, которые мы копируем в <tex>R</tex> памяти на , а по окончании перекопирования поменяем ролями стеки <tex>n + 1Rc, Rc'</tex> операцию, то есть как мы делали со стеками <tex>3=O(1)L, L'</tex> времени и памяти на операцию. Совершая три дополнительных действия вместе с каждой операцией # Как учесть, что во время перекопирования часть элементов была извлечена из <tex>Rc</tex>? Для этого заведем специальную переменную <tex>\mathtt{toCopy}</tex>push, которая показывает, сколько корректных элементов находится в стеке <tex>S</tex>, и уменьшается при каждом извлечении из <tex>S</tex> или операции <tex>\mathtt{pop}</tex> в режиме перекопирования . К счастью, все некорректные элементы будут нарастать со дна стека, так что мы гарантируемникогда не извлечем некорректный элемент, если <tex>\mathtt{toCopy}>0</tex>. Если во время операции <tex>\mathtt{pop}</tex> у нас <tex>\mathtt{toCopy} = 0</tex>, это означает, что перекопирование завершится до опустошения стека теперь в стеке <tex>R'</tex>находится весь правый кусок очереди, так что нам придется извлечь элемент из него.
Теперь проверим корректность условия активации. Пусть среди может возникнуть проблема с непустым <tex>n</tex> следующих за активацией операций <tex>push, pop</tex> присуствует <tex>x</tex> операций <tex>pop</tex> и <tex>n - x</tex> операций <tex>pushRc</tex>после завершения перекопирования. ТогдаПокажем, очевидночто мы всегда успеем его опустошить, если будем использовать дополнительное извлечение из него при каждой операции в стеке <tex>R</tex> окажется <tex>2 \cdot n + 1 - x</tex> элементов, а в стеке <tex>L</tex> станет <tex>n - x</tex> элементовобычном режиме, то есть на <tex>n + 1</tex> элемент меньше, чем в стеке <tex>R</tex>, а значит до следующего перекопирования <tex>n+2</tex> операциидля этого полностью проанализируем алгоритм.
ЗаметимПусть на начало перекопирования в стеке <tex>R</tex> содержится <tex>n</tex> элементов, что теперь если очередь находится тогда в обычном режиме, стеке <tex>L</tex> находится <tex>n + 1</tex> элементов.size Мы корректно можем обработать любое количество операций <tex>\mathtt{push}</tex>, а также <tex>n</tex> операций <tex>\leqslant Rmathtt{pop}</tex>.sizeЗаметим, что операция <tex>\mathtt{empty}</tex> во время перекопирования всегда возвращает <tex>false</tex>, значит так как мы не можем извлекать элементы из стека <tex>empty=(RL</tex>, который не пустой.size=0)Таким образом вместе с операцией, активирующей перекопирование, мы гарантированно можем корректно обработать <tex>n + 1</tex>операцию.
Посмотрим на дополнительные действия, которые нам предстоят:# Переместить содержимое <tex>R</tex> в <tex>S</tex>, <tex>n</tex> действий.# Переместить содержимое <tex>L</tex> в стеки <tex>R, Rc'</tex>, <tex>n + 1</tex> действий.# Переместить первые <tex>\mathtt{toCopy}</tex> элементов из <tex>S</tex> в <tex>R, Rc'</tex>, остальные выкинуть, <tex>n</tex> действий.# Поменять ролями стеки <tex>Rc, Rc'</tex>, <tex>L, L'</tex>, <tex>2</tex> действия. Таким образом, получили <tex>3 \cdot n + 3</tex> дополнительных действия за <tex>n + 1</tex> операций, или <tex>3 = O(1)</tex> дополнительных действий на операцию в режиме перекопирования, что и требовалось. Теперь рассмотрим, как изменились наши стеки за весь период перекопирования. Договоримся, что операция <tex>\mathtt{empty}</tex> не меняет очередь, то есть никакие дополнительные действия не совершаются. Пусть за <tex>n</tex> следующих за активацией меняющих операций (<tex>\mathtt{push}, \mathtt{pop}</tex>) поступило <tex>x</tex> операций <tex>\mathtt{pop}</tex>, <tex>n - x</tex> операций <tex>\mathtt{push}</tex>. Очевидно, что после перекопирования в новых стеках окажется: <tex>n-x</tex> элементов в <tex>L</tex>, <tex>2 \cdot n + 1 - x = (n - x) + (n + 1)</tex> элементов в <tex>R</tex>, то есть до следующего перекопирования еще <tex>n + 2</tex> операции. С другой стороны, стек <tex>Rc</tex> содержал всего <tex>n</tex> элементов, так что мы можем очистить его, просто удаляя по одному элементу при каждой операции в обычном режиме. Итак, очередь <tex>Q</tex> будет состоять из пяти шести стеков <tex>L,L',R,RRc,Rc',TS</tex>, а также двух внутренних переменных <tex>\mathtt{recopy}, \mathtt{toCopy}</tex>, которые нужны для корректности перекопирования+ дополнительная переменная <tex>\mathtt{copied}</tex>, показывающая, перемещали ли мы элементы из стека <tex>L</tex> в стек <tex>R</tex>, чтобы не начать перемещать эти элементы в стек <tex>S</tex>.
Инвариант очереди (обычный режим):
# <tex>L.size \leqslant R.size</tex>
# <tex>R.size = 0 \equiv Q.size = 0</tex>
# <tex>Rc</tex> {{---}} копия <tex>R</tex># <tex>Rc'.size < R.size - L.size</tex># <tex>L'.size = 0, TS.size = 0</tex> Тогда к следующему перекопированию (<tex>L.size=R.size+1</tex>) мы гарантированно будем иметь пустые стеки <tex>L',S,Rc'</tex>, которые нам понадобятся.
Инвариант очереди (режим перекопирования):
# <tex>Rc.size = \mathtt{toCopy}</tex># Если <tex>L.size = 0</tex>, то :## При <tex>\mathtt{toCopy} > 0</tex> первые <tex>\mathtt{toCopy}</tex> элементов <tex>TS</tex> {{---}} корректны, то есть действительно содержатся в очереди.## При <tex>\mathtt{toCopy} \leqslant 0</tex> стек <tex>R</tex> содержит весь правый кусок очереди в правильном порядке.
Очередь будет работать в двух режимах:
# Обычный режим, кладем в <tex>L</tex>, извлекаем из <tex>R</tex>и из <tex>Rc, Rc'</tex> для поддержания порядка, операция <tex>empty = (R.size = 0)</tex>.# Режим перекопирования, кладем в <tex>L'</tex>, извлекаем из <tex>R'Rc</tex>, возможно из <tex>empty=false</tex>, совершаем дополнительные действия. Также после операции в обычном режиме следует проверка на активацию перекопирования(<tex>recopy = (L.size > R.size)</tex>), если это так, <tex>toCopy\mathtt{empty} =R.size, recopy=true, R'=R\mathtt{false}</tex>, совершается первый набор дополнительных действийсовершаем дополнительные действия.
После Также после операции в обычном режиме перекопирования следует проверка на завершение активацию перекопирования (<tex>\mathtt{recopy} =(TL.size > R.size==0)</tex>), а при завершении меняются ролями стеки если это так, <tex>L\mathtt{toCopy} = R.size, \mathtt{recopy} = true, L'\mathtt{copied} = false</tex>, совершается первый набор дополнительных действий.
В качестве версии очереди мы будем использовать запись После операции в режиме перекопирования следует проверка на завершение перекопирования (<tex>Q\mathtt{recopy} =<L, L', R, R', T, recopy, toCopy></tex>, содержащую пять версий стеков и две переменных.Пусть персистентный стек возвращает вместе с обычным результатом стека новую версию, то есть операция <tex>(S.popsize == 0)</tex> возвращает ), а при завершении меняются ролями стеки <tex><SRc, Rc', x></tex>, а операция <tex>S.push</tex> возвращает <tex><SL, L'></tex>.
Следующий псевдокод выполняет требуемые операции:
=== empty ===
<code>
'''boolean''' empty():
'''return''' !recopy '''and''' R.size == 0
</code>
=== push ===
<code>
'''function''' push(x: '''T'''): '''if''' !recopy:
L.push(x)
'''if''' Rc'.size > 0:
Rc'.pop()
checkRecopy()
'''else''':
L'.push(x)
checkNormal()
=== pop ===
<code>
'''T''' pop(): '''if''' !recopy: '''T''' tmp = R.pop()
Rc.pop()
'''if''' Rc'.size > 0:
Rc'.pop()
checkRecopy()
'''return''' tmp
'''else''': '''T''' tmp = Rc.pop() '''if''' toCopy > 0 toCopy = toCopy - 1 '''else''' R.pop() Rc'.pop()
checkNormal()
'''return''' tmp
</code>
 
=== checkRecopy ===
<code>
'''function''' checkRecopy() :
recopy = L.size > R.size
'''if''' recopy:
toCopy = R.size
additionalOperationscopied = false checkNormal()
</code>
 
=== checkNormal ===
<code>
'''function''' checkNormal():
additionalOperations()
<span style="color:#008000">// Если мы не все перекопировали, то у нас не пуст стек TS</span> recopy = TS.size <tex> \ne </tex> 0
</code>
=== additionalOperations ===
<code>
'''function''' additionalOperations(): <span style="color:#008000">// Нам достаточно 3 операций на вызов</span> '''int''' toDo = 3 <span style="color:#008000">// Пытаемся перекопировать R в TS</span> '''while''' '''not''' copied '''and''' toDo > 0 '''and''' R.size > 0: TS.push(R.pop())
toDo = toDo - 1
<span style="color:#008000">// Пытаемся перекопировать L в R и Rc'</span> '''while''' toDo > 0 '''and''' L1L.size > 0: copied = true '''T''' x = L.pop()
R.push(x)
Rc'.push(x)
toDo = toDo - 1
<span style="color:#008000">// Пытаемся перекопировать T S в R и Rc' с учетом toCopy</span> '''while''' toDo > 0 '''and''' TS.size > 0: '''T''' x = TS.pop() '''if''' toCopy > 0:
R.push(x)
Rc'.push(x)
toCopy = toCopy - 1
toDo = toDo - 1
<span style="color:#008000">// Если все скопировано, то меняем роли L1L, L2 L' и Rc1Rc, Rc2Rc'</span> '''if''' TS.size == 0:
swap(L, L')
swap(Rc, Rc')
</code>
== Персистентная очередь на пяти стеках ==
 
После того, как мы получили [[Персистентная очередь#Реализация очереди на шести стеках|очередь в реальном времени]] с <tex>O(1) = 6</tex> обычными стеками, ее можно легко превратить в персистентную, сделав все стеки [[Персистентный стек|персистентными]], но на самом деле персистентность позволяет не создавать явной копии стека <tex>R</tex>, так что достаточно всего пяти стеков.
 
Вместо стеков <tex>Rc, Rc'</tex> персистентная очередь хранит один стек <tex>R'</tex>, в который при активации перекопирования записывается последняя версия стека <tex>R</tex>, в дальнейшем все операции <tex>\mathtt{pop}</tex> обращаются именно к ней. Все замечания о <tex>\mathtt{toCopy}</tex> остаются в силе.
 
Также нам нет необходимости опустошать стек <tex>R'</tex> к моменту перекопирования, так как скопировать туда новую версию <tex>R</tex> мы можем за <tex>O(1)</tex>, а освобождение ячеек памяти бессмысленно, так как они используются в других версиях персистентной очереди.
 
В качестве версии очереди мы будем использовать запись <tex>Q= \langle L, L', R, R', S, \mathtt{recopy}, \mathtt{toCopy}, \mathtt{copied}\rangle</tex>, содержащую пять версий персистентных стеков и три переменных.
 
Пусть персистентный стек возвращает вместе с обычным результатом работы стека новую версию, то есть операция <tex>S.pop</tex> возвращает <tex>\langle Sn, x\rangle</tex>, а операция <tex>S.push(x)</tex> возвращает <tex>Sn</tex>.
 
Аналогично свою новую версию вместе с результатом операции возвращает и персистентная очередь, то есть <tex>Q.pop</tex> возвращает <tex>\langle Qn, x\rangle</tex>, а <tex>Q.push(x)</tex> возвращает <tex>Qn</tex>.
 
Следующий псевдокод выполняет требуемые операции:
=== empty ===
<code>
'''boolean''' empty():
'''return''' !recopy '''and''' R.size == 0
</code>
=== push ===
<code>
'''function''' push(x : '''T'''):
'''if''' !recopy
'''stack<T>''' Ln = L.push(x)
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> Q' = <Ln, L', R, R', S, recopy, toCopy, copied>
'''return''' Q'.checkRecopy()
'''else'''
'''stack<T>''' Ln' = L'.push(x)
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> Q' = <L, Ln', R, R', S, recopy, toCopy, copied>
'''return''' Q'.checkNormal()
</code>
=== pop ===
<code>
<'''stack<T>''', '''T'''> pop():
'''if''' !recopy
<Rn, x> = R.pop()
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> Q' = <L, L', Rn, R', S, recopy, toCopy, copied>
'''return''' <Q'.checkRecopy(), x>
'''else'''
<Rn', x> = R'.pop()
'''int''' curCopy = toCopy
Rn = R
'''if''' toCopy > 0
curCopy = curCopy - 1
'''else'''
<Rn, x> = Rn.pop()
Q' = <L, L', Rn, Rn', S, recopy, curCopy, copied>
'''return''' <Q'.checkNormal(), x>
</code>
=== checkRecopy ===
<code>
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> checkRecopy():
'''if''' L.size > R.size
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> Q' = <L, L', R, R', S, true, R.size, false>
'''return''' Q'.checkNormal()
'''else'''
'''return''' <L, L', R, R', S, false, toCopy, copied>
</code>
 
=== checkNormal ===
<code>
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> checkNormal():
Q' = Q.additionalOperations()
<span style="color:#008000">// Если мы не все перекопировали, то у нас не пуст стек S</span>
return <Q'.L, Q'.L', Q'.R, Q'.R', Q'.S, Q'.S.size <tex> \ne </tex> 0, Q'.toCopy, Q'.copied>
</code>
=== additionalOperations ===
<code>
<'''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''stack<T>''', '''boolean''', '''int''', '''boolean'''> additionalOperations():
<span style="color:#008000">// Нам достаточно 3 операций на вызов</span>
'''int''' toDo = 3
<span style="color:#008000">// Пытаемся перекопировать R в S</span>
'''stack<T>''' Rn = R
'''stack<T>''' Sn = S
'''boolean''' curCopied = copied
'''while''' '''not''' curCopied '''and''' toDo > 0 '''and''' Rn.size > 0
<Rn, x> = Rn.pop()
Sn = Sn.push(x)
toDo = toDo - 1
Ln = L
<span style="color:#008000">// Пытаемся перекопировать L в R</span>
'''while''' toDo > 0 '''and''' Ln.size > 0
curCopied = true
<Ln, x> = Ln.pop()
Rn = Rn.push(x)
toDo = toDo - 1
curCopy = toCopy
<span style="color:#008000">// Пытаемся перекопировать S в R с учетом toCopy</span>
'''while''' toDo > 0 '''and''' Sn.size > 0
<Sn, x> = Sn.pop()
'''if''' curCopy > 0
Rn = Rn.push(x)
curCopy = curCopy - 1
toDo = toDo - 1
'''stack<T>''' Ln' = L'
<span style="color:#008000">// Если все скопировано, то меняем роли L1, L2</span>
'''if''' S.size == 0
swap(Ln, Ln')
'''return''' <Ln, Ln', Rn, R', Sn, recopy, curCopy, curCopied>
</code>
 
== Пример ==
 
Пусть мы создали персистентную очередь. Будем изображать ее в виде пяти деревьев версий персистентных стеков, закрашенные вершины {{---}} текущие версии стеков, соответствующие текущему состоянию очереди; стрелка от стека <tex>R'</tex> указывает на ту версию стека <tex>R</tex>, которая там сейчас хранится. В самих вершинах записаны соответствующие этим вершинам значения.
 
[[Файл:PersistentQueue_state0.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(1)} </tex>, изначально режим обычный, так что элемент пойдет в стек <tex>L</tex>. Эта операция активирует режим перекопирования, в результате которого содержимое <tex>L</tex> перекопируется в стек <tex>R</tex>, после чего перекопирование завершится, стеки <tex>L, L'</tex> поменяются местами.
 
[[Файл:PersistentQueue_state1.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(2)} </tex>, у нас обычный режим, поэтому элемент пойдет в стек <tex>L</tex>, перекопирование не активируется.
 
[[Файл:PersistentQueue_state2.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(3)} </tex>, у нас обычный режим, поэтому элемент пойдет в стек <tex>L</tex>, активируется перекопирование, <tex>R' = R</tex>, за три операции мы успеваем перекопировать элемент стека <tex>R</tex> в стек <tex>S</tex>, а также перекопировать два элемента стека <tex>L</tex> в стек <tex>R</tex>.
 
[[Файл:PersistentQueue_state3.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(4)} </tex>, мы в режиме перекопирования, поэтому элемент пойдет в стек <tex>L'</tex>, далее мы успеваем перекопировать обратно элемент из стека <tex>S</tex> в стек <tex>R</tex>, перекопирование завершается, стеки <tex>L, L'</tex> меняются местами.
 
[[Файл:PersistentQueue_state4.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(5)} </tex>, у нас обычный режим, поэтому элемент пойдет в стек <tex>L</tex>, перекопирование не активируется.
 
[[Файл:PersistentQueue_state5.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(6)} </tex>, у нас обычный режим, поэтому элемент пойдет в стек <tex>L</tex>, перекопирование не активируется.
 
[[Файл:PersistentQueue_state6.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex> \mathtt{push(7)} </tex>, у нас обычный режим, поэтому элемент пойдет в стек <tex>L</tex>, перекопирование активируется, <tex>R' = R</tex>, <tex>\mathtt{toCopy} = 3</tex>, за три операции мы успеваем перекопировать содержимое стека <tex>R</tex> в стек <tex>S</tex>.
 
[[Файл:PersistentQueue_state7.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex>\mathtt{pop}</tex>, мы находимся в режиме перекопирования, так что элемент извлекается из <tex>R'</tex>, <tex>\mathtt{toCopy} = 2</tex>. За три операции мы успеваем перекопировать три элемента стека <tex>L</tex> в стек <tex>R</tex>.
 
[[Файл:PersistentQueue_state8.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex>\mathtt{pop}</tex>, мы находимся в режиме перекопирования, так что элемент извлекается из <tex>R'</tex>, <tex>\mathtt{toCopy} = 1</tex>. За три операции мы успеваем перекопировать один элемент стека <tex>L</tex> в стек <tex>R</tex>, а также извлечь два элемента стека <tex>S</tex>, с учетом <tex>\mathtt{toCopy}</tex> только один элемент попадет в стек <tex>R</tex>, <tex>\mathtt{toCopy} = 0</tex>.
 
[[Файл:PersistentQueue_State9.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
Сделаем операцию <tex>\mathtt{pop}</tex>, мы находимся в режиме перекопирования, так что элемент извлекается из <tex>R'</tex>, но <tex>\mathtt{toCopy} = 0</tex>, так что нам приходится извлечь еше один элемент из стека <tex>R</tex>. Мы извлекаем один элемент из стека <tex>S</tex>, перекопирование заканчивается, стеки <tex>L, L'</tex> меняются местами.
 
[[Файл:PersistentQueue_state10.png|300px|nothumb|left|]]
 
<br clear = "all"/>
 
== См. также ==
*[[Персистентный стек]]
*[[Персистентный дек]]
*[[Персистентная приоритетная очередь]]
 
== Источники информации ==
* [http://hdl.handle.net/1813/6273 ''Hood R., Melville R.'' Real Time Queue Operations in Pure LISP. {{---}} Cornell University, 1980]
* [http://habrahabr.ru/post/241231 Хабрахабр {{---}} Персистентная очередь]
* [http://codeforces.com/blog/entry/15685 Codeforces {{---}} Персистентная очередь и её друзья]
[[Категория: Дискретная математика и алгоритмы]]
[[Категория: Амортизационный анализ]]
[[Категория: Структуры данных‏‎]]
24
правки

Навигация