Как реализовать очередь с тремя стеками?

Я столкнулся с этим вопросом в книге алгоритмов (Алгоритмы, 4-е издание Роберта Седжуика и Кевина Уэйна).

Очередь с тремя стеками. Реализуйте очередь с тремя стеками, чтобы каждая операция очереди принимала постоянное (худшее) количество операций стека. Предупреждение: высокая степень сложности.

Я знаю, как сделать очередь с двумя стеками, но я не могу найти решение с тремя стеками. Есть идеи?

(о, и это не домашнее задание:))

Ответ 1

РЕЗЮМЕ

  • O (1) алгоритм известен для 6 стеков
  • O (1) алгоритм известен тремя стеками, но с использованием ленивой оценки, которая на практике соответствует наличию дополнительных внутренних структур данных, поэтому она не является решением
  • Люди, близкие к Sedgewick, подтвердили, что они не знают о решении из трех стеков во всех ограничениях исходного вопроса.

ОПИСАНИЕ

За этой ссылкой есть две реализации: http://www.eecs.usma.edu/webs/people/okasaki/jfp95/index.html

Один из них - O (1) с тремя стеками, но он использует ленивое выполнение, что на практике создает дополнительные промежуточные структуры данных (закрытия).

Другой из них - O (1), но использует SIX-стеки. Однако он работает без ленивого исполнения.

ОБНОВЛЕНИЕ: бумага Окасаки находится здесь: http://www.eecs.usma.edu/webs/people/okasaki/jfp95.ps, и кажется, что он фактически использует только 2 стека для версии O (1), которая имеет ленивую оценку. Проблема в том, что она действительно основана на ленивой оценке. Вопрос в том, может ли он быть переведен в алгоритм с тремя стеками без ленивой оценки.

ОБНОВЛЕНИЕ: Другой связанный алгоритм описан в статье "Стеки против Декса" Хольгера Петерсена, опубликованной на 7-й ежегодной конференции по вычислительной технике и комбинаторике. Вы можете найти статью в Google Книгах. Проверьте страницы 225-226. Но алгоритм на самом деле не является имитацией в реальном времени, это линейное моделирование очереди с двумя очередями на трех стеках.

gusbro: "Как сказал @Leonel несколько дней назад, я подумал, что было бы честно проверить с профессором Седжуиком, если он знал решение или была какая-то ошибка, поэтому я написал ему. Я только что получил ответ ( хотя и не от него самого, а от коллеги из Принстона), поэтому я хотел бы поделиться со всеми вами. Он в основном сказал, что не знает, какие алгоритмы используют три стека, а также другие ограничения (например, не используя ленивую оценку). алгоритм с использованием 6 стеков, как мы уже знаем, глядя на ответы здесь. Поэтому, я думаю, вопрос по-прежнему открыт для поиска алгоритма (или доказать, что его невозможно найти).

Ответ 2

Хорошо, это действительно сложно, и единственное решение, которое я мог бы придумать, помнит меня о решении Кирков на тест Кобаяши Мару (как-то обманутый): Идея заключается в том, что мы используем стек стеков (и используем его для моделирования списка). Я вызываю операции en/dequeue и push и pop, затем мы получаем:

queue.new() : Stack1 = Stack.new(<Stack>);  
              Stack2 = Stack1;  

enqueue(element): Stack3 = Stack.new(<TypeOf(element)>); 
                  Stack3.push(element); 
                  Stack2.push(Stack3);
                  Stack3 = Stack.new(<Stack>);
                  Stack2.push(Stack3);
                  Stack2 = Stack3;                       

dequeue(): Stack3 = Stack1.pop(); 
           Stack1 = Stack1.pop();
           dequeue() = Stack1.pop()
           Stack1 = Stack3;

isEmtpy(): Stack1.isEmpty();

(StackX = StackY - это не копирование содержимого, а просто копия ссылки. Просто описать его легко. Вы также можете использовать массив из 3 стеков и получить доступ к ним через индекс, там вы просто измените значение индексная переменная). Все находится в O (1) в терминах работы стека.

И да, я знаю его аргументацию, потому что у нас есть не более 3 стеков, но, возможно, это дает другим вам хорошие идеи.

EDIT: Пример объяснения:

 | | | |3| | | |
 | | | |_| | | |
 | | |_____| | |
 | |         | |
 | |   |2|   | |
 | |   |_|   | |
 | |_________| |
 |             |
 |     |1|     |
 |     |_|     |
 |_____________|

Я попробовал здесь немного ASCII-искусства, чтобы показать Stack1.

Каждый элемент обернут в стек одного элемента (поэтому у нас есть только набор стеков стеков).

Вы видите, что мы сначала удаляем первый элемент (стек содержит здесь элементы 1 и 2). Затем добавьте следующий элемент и разверните там 1. Затем мы говорим, что первый стек, в который мы вошли, теперь является нашим новым Stack1. Говорить немного более функционально - это списки, реализующие стопки из двух элементов, где верхний элемент ist cdr и первый/нижний верхний элемент - это автомобиль. Остальные 2 помогают стекам.

Esp tricky - это вставка, так как вам нужно как-то глубоко погрузиться в вложенные стеки, чтобы добавить другой элемент. Вот почему Stack2 есть. Stack2 всегда самый внутренний стек. Добавление - это просто нажатие элемента и последующее нажатие на новый Stack2 (и поэтому нам не разрешено касаться Stack2 в нашей операции dequeue).

Ответ 3

Я попытаюсь доказать, что это невозможно.


Предположим, что есть очередь Q, которая имитируется тремя стеками, A, B и C.

утверждения

  • ASRT0: = Кроме того, предположим, что Q может имитировать операции {queue, dequeue} в O (1). Это означает, что существует определенная последовательность стека push/pops для каждой операции очереди/деактивации.

  • Без ограничения общности предположим, что операции очереди детерминированы.

Пусть элементы, помещенные в Q, пронумерованы 1, 2,..., на основе их порядка очереди, причем первый элемент, который помещен в Q, определяется как 1, второй - как 2 и так далее.

Определить

  • Q(0) := Состояние Q, когда в Q есть 0 элементов и, следовательно, 0 элементов в A, B и C)
  • Q(1) := Состояние Q (и A, B и C) после 1 операции очереди на Q(0)
  • Q(n) := Состояние Q (и A, B и C) после n операций очереди на Q(0)

Определить

  • |Q(n)| := количество элементов в Q(n) (поэтому |Q(n)| = n)
  • A(n) := состояние стека A, когда состояние Q равно Q(n)
  • |A(n)| := количество элементов в A(n)

И аналогичные определения для стеков B и C.

Тривиально,

|Q(n)| = |A(n)| + |B(n)| + |C(n)|

---

|Q(n)|, очевидно, неограниченно на n.

Следовательно, по крайней мере один из |A(n)|, |B(n)| или |C(n)| неограничен на n.

WLOG1, предположим, что стек A неограничен, а стеки B и C ограничены.

Define * B_u := верхняя граница B * C_u := верхняя граница C * K := B_u + C_u + 1

WLOG2, для n таких, что |A(n)| > K, выберите K элементов из Q(n). Предположим, что 1 из этих элементов находится в A(n + x), для всех x >= 0, т.е. Элемент всегда находится в стеке A, независимо от того, сколько операций очереди выполняется.

  • X := этот элемент

Тогда мы можем определить

  • Abv(n) := количество элементов в стеке A(n), которое выше X
  • Blo(n) := количество элементов в стеке A(n), которое меньше X

    | А (п) | = Abv (n) + Blo (n)

ASRT1 := Число всплывающих окон, требуемых для удаления X из Q(n), составляет не менее Abv(n)

Из (ASRT0) и (ASRT1), ASRT2 := Abv(n) должно быть ограничено.

Если Abv(n) неограничен, то если для удаления X из Q(n) требуется 20 делений, для этого потребуется не менее Abv(n)/20 всплывающих окон. Это неограниченно. 20 может быть любой константой.

Таким образом,

ASRT3 := Blo(n) = |A(n)| - Abv(n)

должен быть неограниченным.


WLOG3, мы можем выбрать элементы K со дна A(n), а один из них находится в A(n + x) для всех x >= 0

X(n) := этот элемент для любого заданного n

ASRT4 := Abv(n) >= |A(n)| - K

Всякий раз, когда элемент помещается в очередь Q(n)...

WLOG4, пусть B и C уже заполнены до их верхних границ. Предположим, что верхняя граница для элементов выше X(n) достигнута. Затем новый элемент входит в A.

WLOG5, предположим, что в результате новый элемент должен войти ниже X(n).

ASRT5 := Количество pops, необходимых для установки элемента ниже X(n) >= Abv(X(n))

От (ASRT4), Abv(n) неограниченно на n.

Следовательно, количество pops, необходимых для размещения элемента ниже X(n), не ограничено.


Это противоречит ASRT1, поэтому невозможно смоделировать очередь O(1) с тремя стеками.


т.е.

Не менее 1 стек должен быть неограниченным.

Для элемента, который находится в этом стеке, количество элементов над ним должно быть ограничено, или операция удаления для удаления этого элемента будет неограниченной.

Однако, если число элементов над ним ограничено, то оно достигнет предела. В какой-то момент новый элемент должен войти под ним.

Поскольку мы всегда можем выбрать старый элемент из одного из немногих элементов этого стека, над ним может быть неограниченное количество элементов (на основе неограниченного размера неограниченного стека).

Чтобы ввести новый элемент под ним, поскольку над ним существует неограниченное количество элементов, для размещения нового элемента под ним требуется неограниченное количество всплывающих окон.

И, следовательно, противоречие.


Существует 5 операторов WLOG (без ограничения общности). В некотором смысле, они могут быть интуитивно поняты как истинные (но учитывая, что им 5, это может занять некоторое время). Формальное доказательство того, что никакая общность не теряется, может быть выведена, но чрезвычайно длинна. Они опущены.

Я признаю, что такое упущение может оставить оговорки WLOG. С помощью программы-параноии для ошибок, пожалуйста, проверяйте операторы WLOG, если хотите.

Третий стек также не имеет значения. Важно то, что существует набор ограниченных стеков и набор неограниченных стеков. Минимальным, необходимым для примера, является 2 стека. Количество стеков должно быть, конечно, конечным.

Наконец, если я прав, что нет доказательств, тогда должно быть более простое индуктивное доказательство. Вероятно, основываясь на том, что происходит после каждой очереди (отслеживайте, как это влияет на наихудший случай dequeue, учитывая набор всех элементов в очереди).

Ответ 4

Примечание. Это значит, что это комментарий к функциональной реализации очередей в режиме реального времени (с постоянным временем наихудшего случая) с односвязными списками. Я не могу добавлять комментарии из-за репутации, но было бы неплохо, если бы кто-то мог изменить это на комментарий, добавленный в ответ antti.huima. Опять же, это несколько длинный комментарий.

@antti.huima: Связанные списки не совпадают со стеком.

  • s1 = (1 2 3 4) --- связанный список с 4 узлами, каждый из которых указывает на один справа, и удерживая значения 1, 2, 3 и 4

  • s2 = popped (s1) --- s2 теперь (2 3 4)

В этот момент s2 эквивалентно popped (s1), который ведет себя как стек. Однако s1 все еще доступен для справки!

  • s3 = popped (popped (s1)) --- s3 is (3 4)

Мы все еще можем заглянуть в s1, чтобы получить 1, тогда как в правильной реализации стека элемент 1 исчез из s1!

Что это значит?

  • s1 - ссылка на верхнюю часть стека
  • s2 - ссылка на второй элемент стека
  • s3 является ссылкой на третий...

Дополнительные связанные списки, созданные в настоящее время, служат в качестве ссылки/указателя! Конечное число стеков не может этого сделать.

Из того, что я вижу в документах/коде, все алгоритмы используют это свойство связанных списков для сохранения ссылок.

Изменить: я имею в виду только 2 и 3 алгоритма связанных списков, используя это свойство связанных списков, поскольку я читал их сначала (они выглядели проще). Это не означает, что они являются или не применимы, просто чтобы объяснить, что связанные списки не обязательно идентичны. Я прочитаю книгу с 6, когда я свободна. @Welbog: Спасибо за исправление.


Лень может также имитировать функции указателя аналогичным образом.


Использование связанного списка решает другую проблему. Эта стратегия может использоваться для реализации очередей в режиме реального времени в Lisp (или, по крайней мере, Lisps, которые настаивают на построении всего из связанных списков): см. "Операции очереди в реальном времени в Pure Lisp" (связанные с antti. huima). Это также хороший способ создания неизменяемых списков с O (1) временем работы и совместно используемыми (неизменяемыми) структурами.

Ответ 5

Вы можете сделать это в режиме амортизации с двумя стеками:

------------- --------------
            | |
------------- --------------

Добавление O(1) и удаление O(1), если сторона, которую вы хотите удалить, не пуста и O(n) в противном случае (разделите другой стек на два).

Хитрость заключается в том, чтобы увидеть, что операция O(n) будет выполняться только через каждые O(n) (если вы разделите, например, пополам). Следовательно, среднее время для операции O(1)+O(n)/O(n) = O(1).

Хотя это может выглядеть как проблема, если вы используете императивный язык со стеком на основе массива (самый быстрый), вы все равно будете иметь только амортизированное постоянное время.