Персистентная очередь — различия между версиями
Genyaz (обсуждение | вклад) (Уменьшены изображения) |
Genyaz (обсуждение | вклад) (правки) |
||
Строка 1: | Строка 1: | ||
− | '''Персистентная очередь''' {{---}} это [[Очередь|очередь]], реализующая персистентность, то есть позволяющая получить доступ ко всем своим предыдущим версиям. Как будет показано далее, можно реализовать функциональную персистентность, то есть каждая ячейка памяти в такой структуре будет инициализирована один раз и в дальнейшем не | + | '''Персистентная очередь''' {{---}} это [[Очередь|очередь]], реализующая персистентность, то есть позволяющая получить доступ ко всем своим предыдущим версиям. Как будет показано далее, можно реализовать функциональную персистентность, то есть каждая ячейка памяти в такой структуре будет инициализирована один раз и в дальнейшем не изменится. |
= Основная идея = | = Основная идея = | ||
− | Для создания персистентной очереди очень удобно пользоваться ее реализацией на стеках, поскольку стеки легко сделать персистентными. Реализация на двух стеках не подходит для этого, так как в худшем сучае требует <tex>O(n)</tex> времени, а значит и <tex>O(n)</tex> памяти в случае персистентности на операцию. Покажем сначала как создать очередь в реальном времени с <tex>O(1)</tex> времени на операцию, а затем превратим ее в персистентную. | + | Для создания персистентной очереди очень удобно пользоваться ее реализацией на стеках, поскольку стеки легко сделать персистентными, причем в этом случае мы добьемся функциональной персистетнтности. Реализация на двух стеках не подходит для этого, так как в худшем сучае требует <tex>O(n)</tex> времени, а значит и <tex>O(n)</tex> памяти в случае персистентности на операцию. Покажем сначала как создать очередь в реальном времени с <tex>O(1)</tex> времени на операцию, а затем превратим ее в персистентную. |
== Реализация очереди на шести стеках == | == Реализация очереди на шести стеках == | ||
Строка 11: | Строка 11: | ||
Сначала будем действовать аналогично случаю с двумя стеками. Пусть у нас есть стек <tex>L</tex> для операций <tex>push</tex> и стек <tex>R</tex> для операций <tex>pop</tex>. К моменту опустошения стека <tex>R</tex> нам нужно успеть получить стек <tex>R'</tex>, содержащий текущие элементы стека <tex>L</tex> в правильном для извлечения порядке. Перекопирование (''recopy mode'') начнется, когда появится опасность того, что мы не сможем за оставшиеся <tex>R.size</tex> операций <tex>pop</tex> со стеком <tex>R</tex> перекопировать стек <tex>L</tex> в новый стек <tex>R'</tex>. Очевидно, это ситуация <tex>L.size>R.size</tex>, пусть такое состояние отражает специальная переменная логического типа <tex>recopy</tex>. | Сначала будем действовать аналогично случаю с двумя стеками. Пусть у нас есть стек <tex>L</tex> для операций <tex>push</tex> и стек <tex>R</tex> для операций <tex>pop</tex>. К моменту опустошения стека <tex>R</tex> нам нужно успеть получить стек <tex>R'</tex>, содержащий текущие элементы стека <tex>L</tex> в правильном для извлечения порядке. Перекопирование (''recopy mode'') начнется, когда появится опасность того, что мы не сможем за оставшиеся <tex>R.size</tex> операций <tex>pop</tex> со стеком <tex>R</tex> перекопировать стек <tex>L</tex> в новый стек <tex>R'</tex>. Очевидно, это ситуация <tex>L.size>R.size</tex>, пусть такое состояние отражает специальная переменная логического типа <tex>recopy</tex>. | ||
− | Понятно, что во время перекопирования могут поступить операции <tex>push</tex>, а стек <tex>L</tex> в это время потеряет свою структуру, сложить элементы туда мы уже не сможем, значит нужно завести еще один стек <tex>L'</tex>, в который мы и будем складывать новые элементы. После окончания перекопирования мы поменяем ролями <tex>L,L'</tex> и <tex>R,R'</tex>, | + | Понятно, что во время перекопирования могут поступить операции <tex>push</tex>, а стек <tex>L</tex> в это время потеряет свою структуру, сложить элементы туда мы уже не сможем, значит нужно завести еще один стек <tex>L'</tex>, в который мы и будем складывать новые элементы. После окончания перекопирования мы поменяем ролями <tex>L,L'</tex> и <tex>R,R'</tex>, на первый взгляд, все станет хорошо. |
Однако, если реализовать этот алгоритм, мы получим неприятную вещь: старый стек <tex>R</tex> может и не опустошиться за это время, то есть мы получили два стека с выходными данными, а значит, возможен случай (например, если все поступающие операции {{---}} <tex>push</tex>), когда при следующем перекопировании у нас не будет свободного стека для копировании туда элементов <tex>L</tex>. Для преодоления этой проблемы мы принудительно будем извлекать все элементы из стека <tex>R</tex> во вспомогательный стек <tex>T</tex>, затем копировать элементы из стека <tex>L</tex> в <tex>R</tex>, а затем обратно копировать элементы из стека <tex>T</tex> в <tex>R</tex>. Легко показать, что приведенный алгоритм как раз получает на выходе в <tex>R</tex> все элементы стеков <tex>L,R</tex> в правильном порядке. | Однако, если реализовать этот алгоритм, мы получим неприятную вещь: старый стек <tex>R</tex> может и не опустошиться за это время, то есть мы получили два стека с выходными данными, а значит, возможен случай (например, если все поступающие операции {{---}} <tex>push</tex>), когда при следующем перекопировании у нас не будет свободного стека для копировании туда элементов <tex>L</tex>. Для преодоления этой проблемы мы принудительно будем извлекать все элементы из стека <tex>R</tex> во вспомогательный стек <tex>T</tex>, затем копировать элементы из стека <tex>L</tex> в <tex>R</tex>, а затем обратно копировать элементы из стека <tex>T</tex> в <tex>R</tex>. Легко показать, что приведенный алгоритм как раз получает на выходе в <tex>R</tex> все элементы стеков <tex>L,R</tex> в правильном порядке. | ||
Строка 70: | Строка 70: | ||
<code> | <code> | ||
push(x) | push(x) | ||
− | '''if''' !recopy | + | '''if''' !recopy |
L.push(x) | L.push(x) | ||
− | '''if''' Rc'.size > 0 | + | '''if''' Rc'.size > 0 |
Rc'.pop() | Rc'.pop() | ||
checkRecopy() | checkRecopy() | ||
− | '''else''' | + | '''else''' |
L'.push(x) | L'.push(x) | ||
checkNormal() | checkNormal() | ||
Строка 82: | Строка 82: | ||
<code> | <code> | ||
pop() | pop() | ||
− | '''if''' !recopy | + | '''if''' !recopy |
tmp = R.pop() | tmp = R.pop() | ||
Rc.pop() | Rc.pop() | ||
− | '''if''' Rc'.size > 0 | + | '''if''' Rc'.size > 0 |
Rc'.pop() | Rc'.pop() | ||
checkRecopy() | checkRecopy() | ||
'''return''' tmp | '''return''' tmp | ||
− | '''else''' | + | '''else''' |
tmp = Rc.pop() | tmp = Rc.pop() | ||
− | '''if''' toCopy > 0 | + | '''if''' toCopy > 0 |
toCopy = toCopy - 1 | toCopy = toCopy - 1 | ||
− | '''else''' | + | '''else''' |
R.pop() | R.pop() | ||
Rc'.pop() | Rc'.pop() | ||
Строка 104: | Строка 104: | ||
checkRecopy() | checkRecopy() | ||
recopy = L.size > R.size | recopy = L.size > R.size | ||
− | '''if''' recopy | + | '''if''' recopy |
toCopy = R.size | toCopy = R.size | ||
copied = false | copied = false | ||
Строка 123: | Строка 123: | ||
toDo = 3 | toDo = 3 | ||
// Пытаемся перекопировать R в T | // Пытаемся перекопировать R в T | ||
− | '''while''' '''not''' copied '''and''' toDo > 0 '''and''' R.size > 0 | + | '''while''' '''not''' copied '''and''' toDo > 0 '''and''' R.size > 0 |
T.push(R.pop()) | T.push(R.pop()) | ||
toDo = toDo - 1 | toDo = toDo - 1 | ||
// Пытаемся перекопировать L в R и Rc' | // Пытаемся перекопировать L в R и Rc' | ||
− | '''while''' toDo > 0 '''and''' L.size > 0 | + | '''while''' toDo > 0 '''and''' L.size > 0 |
copied = true | copied = true | ||
x = L.pop() | x = L.pop() | ||
Строка 134: | Строка 134: | ||
toDo = toDo - 1 | toDo = toDo - 1 | ||
// Пытаемся перекопировать T в R и Rc' с учетом toCopy | // Пытаемся перекопировать T в R и Rc' с учетом toCopy | ||
− | '''while''' toDo > 0 '''and''' T.size > 0 | + | '''while''' toDo > 0 '''and''' T.size > 0 |
x = T.pop() | x = T.pop() | ||
− | '''if''' toCopy > 0 | + | '''if''' toCopy > 0 |
R.push(x) | R.push(x) | ||
Rc'.push(x) | Rc'.push(x) | ||
Строка 142: | Строка 142: | ||
toDo = toDo - 1 | toDo = toDo - 1 | ||
// Если все скопировано, то меняем роли L, L' и Rc, Rc' | // Если все скопировано, то меняем роли L, L' и Rc, Rc' | ||
− | '''if''' T.size == 0 | + | '''if''' T.size == 0 |
swap(L, L') | swap(L, L') | ||
swap(Rc, Rc') | swap(Rc, Rc') | ||
Строка 168: | Строка 168: | ||
<code> | <code> | ||
push(x) | push(x) | ||
− | '''if''' !recopy | + | '''if''' !recopy |
Ln = L.push(x) | Ln = L.push(x) | ||
Q' = <Ln, L', R, R', T, recopy, toCopy, copied> | Q' = <Ln, L', R, R', T, recopy, toCopy, copied> | ||
'''return''' Q'.checkRecopy() | '''return''' Q'.checkRecopy() | ||
− | '''else''' | + | '''else''' |
Ln' = L'.push(x) | Ln' = L'.push(x) | ||
Q' = <L, Ln', R, R', T, recopy, toCopy, copied> | Q' = <L, Ln', R, R', T, recopy, toCopy, copied> | ||
Строка 180: | Строка 180: | ||
<code> | <code> | ||
pop() | pop() | ||
− | '''if''' !recopy | + | '''if''' !recopy |
<Rn, x> = R.pop() | <Rn, x> = R.pop() | ||
Q' = <L, L', Rn, R', T, recopy, toCopy, copied> | Q' = <L, L', Rn, R', T, recopy, toCopy, copied> | ||
'''return''' <Q'.checkRecopy(), x> | '''return''' <Q'.checkRecopy(), x> | ||
− | '''else''' | + | '''else''' |
<Rn', x> = R'.pop() | <Rn', x> = R'.pop() | ||
curCopy = toCopy | curCopy = toCopy | ||
Rn = R | Rn = R | ||
− | '''if''' toCopy > 0 | + | '''if''' toCopy > 0 |
curCopy = curCopy - 1 | curCopy = curCopy - 1 | ||
− | '''else''' | + | '''else''' |
<Rn, x> = Rn.pop() | <Rn, x> = Rn.pop() | ||
Q' = <L, L', Rn, Rn', T, recopy, curCopy, copied> | Q' = <L, L', Rn, Rn', T, recopy, curCopy, copied> | ||
Строка 198: | Строка 198: | ||
<code> | <code> | ||
checkRecopy() | checkRecopy() | ||
− | '''if''' L.size > R.size | + | '''if''' L.size > R.size |
Q' = <L, L', R, R', T, true, R.size, false> | Q' = <L, L', R, R', T, true, R.size, false> | ||
'''return''' Q'.checkNormal() | '''return''' Q'.checkNormal() | ||
− | '''else''' | + | '''else''' |
'''return''' <L, L', R, R', T, false, toCopy, copied> | '''return''' <L, L', R, R', T, false, toCopy, copied> | ||
</code> | </code> | ||
Строка 221: | Строка 221: | ||
Tn = T | Tn = T | ||
curCopied = copied | curCopied = copied | ||
− | '''while''' '''not''' curCopied '''and''' toDo > 0 '''and''' Rn.size > 0 | + | '''while''' '''not''' curCopied '''and''' toDo > 0 '''and''' Rn.size > 0 |
<Rn, x> = Rn.pop() | <Rn, x> = Rn.pop() | ||
Tn = Tn.push(x) | Tn = Tn.push(x) | ||
Строка 227: | Строка 227: | ||
Ln = L | Ln = L | ||
// Пытаемся перекопировать L в R | // Пытаемся перекопировать L в R | ||
− | '''while''' toDo > 0 '''and''' Ln.size > 0 | + | '''while''' toDo > 0 '''and''' Ln.size > 0 |
curCopied = true | curCopied = true | ||
<Ln, x> = Ln.pop() | <Ln, x> = Ln.pop() | ||
Строка 234: | Строка 234: | ||
curCopy = toCopy | curCopy = toCopy | ||
// Пытаемся перекопировать T в R с учетом toCopy | // Пытаемся перекопировать T в R с учетом toCopy | ||
− | '''while''' toDo > 0 '''and''' Tn.size > 0 | + | '''while''' toDo > 0 '''and''' Tn.size > 0 |
<Tn, x> = Tn.pop() | <Tn, x> = Tn.pop() | ||
− | '''if''' curCopy > 0 | + | '''if''' curCopy > 0 |
Rn = Rn.push(x) | Rn = Rn.push(x) | ||
curCopy = curCopy - 1 | curCopy = curCopy - 1 | ||
Строка 242: | Строка 242: | ||
Ln' = L' | Ln' = L' | ||
// Если все скопировано, то меняем роли L1, L2 | // Если все скопировано, то меняем роли L1, L2 | ||
− | '''if''' T.size == 0 | + | '''if''' T.size == 0 |
swap(Ln, Ln') | swap(Ln, Ln') | ||
'''return''' <Ln, Ln', Rn, R', Tn, recopy, curCopy, curCopied> | '''return''' <Ln, Ln', Rn, R', Tn, recopy, curCopy, curCopied> |
Версия 08:54, 11 июня 2013
Персистентная очередь — это очередь, реализующая персистентность, то есть позволяющая получить доступ ко всем своим предыдущим версиям. Как будет показано далее, можно реализовать функциональную персистентность, то есть каждая ячейка памяти в такой структуре будет инициализирована один раз и в дальнейшем не изменится.
Содержание
Основная идея
Для создания персистентной очереди очень удобно пользоваться ее реализацией на стеках, поскольку стеки легко сделать персистентными, причем в этом случае мы добьемся функциональной персистетнтности. Реализация на двух стеках не подходит для этого, так как в худшем сучае требует
времени, а значит и памяти в случае персистентности на операцию. Покажем сначала как создать очередь в реальном времени с времени на операцию, а затем превратим ее в персистентную.Реализация очереди на шести стеках
Одним из минусов реализации на двух стеках является то, что в худшем случае мы тратим
времени на операцию. Если распределить время, необходимое для перемещения элементов из одного стека в другой, по операциям, мы получим очередь без худших случаев с истинного времени на операцию.Сначала будем действовать аналогично случаю с двумя стеками. Пусть у нас есть стек
для операций и стек для операций . К моменту опустошения стека нам нужно успеть получить стек , содержащий текущие элементы стека в правильном для извлечения порядке. Перекопирование (recopy mode) начнется, когда появится опасность того, что мы не сможем за оставшиеся операций со стеком перекопировать стек в новый стек . Очевидно, это ситуация , пусть такое состояние отражает специальная переменная логического типа .Понятно, что во время перекопирования могут поступить операции
, а стек в это время потеряет свою структуру, сложить элементы туда мы уже не сможем, значит нужно завести еще один стек , в который мы и будем складывать новые элементы. После окончания перекопирования мы поменяем ролями и , на первый взгляд, все станет хорошо.Однако, если реализовать этот алгоритм, мы получим неприятную вещь: старый стек
может и не опустошиться за это время, то есть мы получили два стека с выходными данными, а значит, возможен случай (например, если все поступающие операции — ), когда при следующем перекопировании у нас не будет свободного стека для копировании туда элементов . Для преодоления этой проблемы мы принудительно будем извлекать все элементы из стека во вспомогательный стек , затем копировать элементы из стека в , а затем обратно копировать элементы из стека в . Легко показать, что приведенный алгоритм как раз получает на выходе в все элементы стеков в правильном порядке.Но этого еще недостаточно. Если мы принудительно извлекаем элементы из стека
, появляются следующие проблемы:- Что вернуть при операции ? Для этого заведем себе стек — копию стека , из которого мы и будем извлекать требуемые элементы.
- Как поддерживать корректность такой копии? Поскольку этот стек нужен только для перекопирования, а во время него он занят, нужна запасная копия для копирования всех элементов, которые мы копируем в , а по окончании перекопирования поменяем ролями стеки , как мы делали со стеками .
- Как учесть, что во время перекопирования часть элементов была извлечена из ? Для этого заведем специальную переменную , которая показывает, сколько корректных элементов находится в стеке , и уменьшается при каждом извлечении из или операции . К счастью, все некорректные элементы будут нарастать со дна стека, так что мы никогда не извлечем некорректный элемент, если . Если во время операции у нас , это означает, что теперь в стеке находится весь правый кусок очереди, так что нам придется извлечь элемент из него.
Теперь может возникнуть проблема с непустым
после завершения перекопирования. Покажем, что мы всегда успеем его опустошить, если будем использовать дополнительное извлечение из него при каждой операции в обычном режиме, для этого полностью проанализируем алгоритм.Пусть на начало перекопирования в стеке
содержится элементов, тогда в стеке находится элементов. Мы корректно можем обработать любое количество операций , а также операций . Заметим, что операция во время перекопирования всегда возвращает , так как мы не можем извлекать элементы из стека , который не пустой. Таким образом вместе с операцией, активирующей перекопирование, мы гарантированно можем корректно обработать операцию.Посмотрим на дополнительные действия, которые нам предстоят:
- Переместить содержимое в , действий.
- Переместить содержимое в стеки , действий.
- Переместить первые элементов из в , остальные выкинуть, действий.
- Поменять ролями стеки , , действия.
Таким образом, получили
дополнительных действия за операций, или дополнительных действий на операцию в режиме перекопирования, что и требовалось.Теперь рассмотрим, как изменились наши стеки за весь период перекопирования. Договоримся, что операция
не меняет очередь, то есть никакие дополнительные действия не совершаются. Пусть за следующих за активацией меняющих операций ( ) поступило операций , операций . Очевидно, что после перекопирования в новых стеках окажется: элементов в , элементов в , то есть до следующего перекопирования еще операции. С другой стороны, стек содержал всего элементов, так что мы можем очистить его, просто удаляя по одному элементу при каждой операции в обычном режиме.Итак, очередь
будет состоять из шести стеков , а также двух внутренних переменных , которые нужны для корректности перекопирования + дополнительная переменная , показывающая, перемещали ли мы элементы из стека в стек , чтобы не начать перемещать эти элементы в стек .Инвариант очереди (обычный режим):
- Стек содержит левую половину очереди, порядок при извлечении обратный.
- Стек содержит правую половину очереди, порядок при извлечении прямой.
- — копия
Тогда к следующему перекопированию (
) мы гарантированно будем иметь пустые стеки , которые нам понадобятся.Инвариант очереди (режим перекопирования):
- Если
- При первые элементов — корректны, то есть действительно содержатся в очереди.
- При стек содержит весь правый кусок очереди в правильном порядке.
, то:
Очередь будет работать в двух режимах:
- Обычный режим, кладем в , извлекаем из и из для поддержания порядка, операция .
- Режим перекопирования, кладем в , извлекаем из , возможно из , , совершаем дополнительные действия.
Также после операции в обычном режиме следует проверка на активацию перекопирования (
), если это так, , совершается первый набор дополнительных действий.После операции в режиме перекопирования следует проверка на завершение перекопирования (
), а при завершении меняются ролями стеки , .Следующий псевдокод выполняет требуемые операции:
empty
empty() return !recopy and R.size == 0
push
push(x) if !recopy L.push(x) if Rc'.size > 0 Rc'.pop() checkRecopy() else L'.push(x) checkNormal()
pop
pop() if !recopy tmp = R.pop() Rc.pop() if Rc'.size > 0 Rc'.pop() checkRecopy() return tmp else tmp = Rc.pop() if toCopy > 0 toCopy = toCopy - 1 else R.pop() Rc'.pop() checkNormal() return tmp
checkRecopy
checkRecopy() recopy = L.size > R.size if recopy toCopy = R.size copied = false checkNormal()
checkNormal
checkNormal()
additionalOperations()
// Если мы не все перекопировали, то у нас не пуст стек T
recopy = T.size
0
additionalOperations
additionalOperations() // Нам достаточно 3 операций на вызов toDo = 3 // Пытаемся перекопировать R в T while not copied and toDo > 0 and R.size > 0 T.push(R.pop()) toDo = toDo - 1 // Пытаемся перекопировать L в R и Rc' while toDo > 0 and L.size > 0 copied = true x = L.pop() R.push(x) Rc'.push(x) toDo = toDo - 1 // Пытаемся перекопировать T в R и Rc' с учетом toCopy while toDo > 0 and T.size > 0 x = T.pop() if toCopy > 0 R.push(x) Rc'.push(x) toCopy = toCopy - 1 toDo = toDo - 1 // Если все скопировано, то меняем роли L, L' и Rc, Rc' if T.size == 0 swap(L, L') swap(Rc, Rc')
Персистентная очередь на пяти стеках
После того, как мы получили очередь в реальном времени с обычными стеками, ее можно легко превратить в персистентную, сделав все стеки персистентными, но на самом деле персистентность позволяет не создавать явной копии стека , так что достаточно всего пяти стеков.
Вместо стеков
персистентная очередь хранит один стек , в который при активации перекопирования записывается последняя версия стека , в дальнейшем все операции обращаются именно к ней. Все замечания о остаются в силе.Также нам нет необходимости опустошать стек
к моменту перекопирования, так как скопировать туда новую версию мы можем за , а освобождение ячеек памяти бессмысленно, так как они используются в других версиях персистентной очереди.В качестве версии очереди мы будем использовать запись
, содержащую пять версий персистентных стеков и три переменных.Пусть персистентный стек возвращает вместе с обычным результатом работы стека новую версию, то есть операция
возвращает , а операция возвращает , аналогично свою новую версию вместе с результатом операции возвращает и персистентная очередь.Следующий псевдокод выполняет требуемые операции:
empty
empty() return !recopy and R.size == 0
push
push(x) if !recopy Ln = L.push(x) Q' = <Ln, L', R, R', T, recopy, toCopy, copied> return Q'.checkRecopy() else Ln' = L'.push(x) Q' = <L, Ln', R, R', T, recopy, toCopy, copied> return Q'.checkNormal()
pop
pop() if !recopy <Rn, x> = R.pop() Q' = <L, L', Rn, R', T, recopy, toCopy, copied> return <Q'.checkRecopy(), x> else <Rn', x> = R'.pop() curCopy = toCopy Rn = R if toCopy > 0 curCopy = curCopy - 1 else <Rn, x> = Rn.pop() Q' = <L, L', Rn, Rn', T, recopy, curCopy, copied> return <Q'.checkNormal(), x>
checkRecopy
checkRecopy() if L.size > R.size Q' = <L, L', R, R', T, true, R.size, false> return Q'.checkNormal() else return <L, L', R, R', T, false, toCopy, copied>
checkNormal
checkNormal()
Q' = Q.additionalOperations()
// Если мы не все перекопировали, то у нас не пуст стек T
return <Q'.L, Q'.L', Q'.R, Q'.R', Q'.T, Q'.T.size
0, Q'.toCopy, Q'.copied>
additionalOperations
additionalOperations() // Нам достаточно 3 операций на вызов toDo = 3 // Пытаемся перекопировать R в T Rn = R Tn = T curCopied = copied while not curCopied and toDo > 0 and Rn.size > 0 <Rn, x> = Rn.pop() Tn = Tn.push(x) toDo = toDo - 1 Ln = L // Пытаемся перекопировать L в R while toDo > 0 and Ln.size > 0 curCopied = true <Ln, x> = Ln.pop() Rn = Rn.push(x) toDo = toDo - 1 curCopy = toCopy // Пытаемся перекопировать T в R с учетом toCopy while toDo > 0 and Tn.size > 0 <Tn, x> = Tn.pop() if curCopy > 0 Rn = Rn.push(x) curCopy = curCopy - 1 toDo = toDo - 1 Ln' = L' // Если все скопировано, то меняем роли L1, L2 if T.size == 0 swap(Ln, Ln') return <Ln, Ln', Rn, R', Tn, recopy, curCopy, curCopied>
Пример
Пусть мы создали персистентную очередь. Будем изображать ее в виде пяти персистентных стеков, закрашенные вершины — текущие версии стеков, соответствующие текущему состоянию очереди; стрелка от стека
указывает на ту версию стека , которая там сейчас хранится. В самих вершинах записаны соответствующие этим вершинам значения.
Сделаем операцию
, изначально режим обычный, так что элемент пойдет в стек . Эта операция активирует режим перекопирования, в результате которого содержимое переместится в стек , после чего перекопирование завершится, стеки поменяются местами.
Сделаем операцию
, у нас обычный режим, поэтому элемент пойдет в стек , перекопирование не активируется.
Сделаем операцию
, у нас обычный режим, поэтому элемент пойдет в стек , активируется перекопирование, , за три операции мы успеваем переложить элемент стека в стек , а также переложить два элемента стека в стек .
Сделаем операцию
, мы в режиме перекопирования, поэтому элемент пойдет в стек , далее мы успеваем перекопировать обратно элемент из стека в стек , перекопирование завершается, стеки меняются местами.
Сделаем операцию
, у нас обычный режим, поэтому элемент пойдет в стек , перекопирование не активируется.
Сделаем операцию
, у нас обычный режим, поэтому элемент пойдет в стек , перекопирование не активируется.
Сделаем операцию
, у нас обычный режим, поэтому элемент пойдет в стек , перекопирование активируется, , , за три операции мы успеваем переместить содержимое стека в стек .
Сделаем операцию
, мы находимся в режиме перекопирования, так что элемент извлекается из , . За три операции мы успеваем перекопировать три элемента стека в стек .
Сделаем операцию
, мы находимся в режиме перекопирования, так что элемент извлекается из , . За три операции мы успеваем перекопировать один элемент стека в стек , а также извлечь два элемента стека , с учетом только один элемент попадет в стек , .
Сделаем операцию
, мы находимся в режиме перекопирования, так что элемент извлекается из , но , так что нам приходится извлечь еше один элемент из стека . Мы извлекаем один элемент из стека , перекопирование заканчивается, стеки меняются местами.