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


136

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

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

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

(о, и это не домашняя работа :))


30
Я думаю, что это вариант Ханойской башни .
Гамбо

14
@Jason: Этот вопрос не является дубликатом, так как он запрашивает O (1) амортизированное время, в то время как этот вопрос требует O (1) наихудшего случая для каждой операции. Решение для двух стеков DuoSRX - это время амортизации O (1) на одну операцию.
междневное

15
Автор, конечно же, не шутил, когда сказал: «Предупреждение: высокая степень сложности».
BoltClock

9
@ Гамбо, к сожалению, временная сложность Ханойской башни далеко не постоянна!
prusswan

12
Примечание. Вопрос в тексте был обновлен следующим образом: Реализуйте очередь с постоянным количеством стеков [не «3»], чтобы каждая операция очереди занимала постоянное (в худшем случае) количество операций стека. Предупреждение: высокая степень сложности. ( algs4.cs.princeton.edu/13stacks - раздел 1.3.43). Похоже, что профессор Седжвик признал первоначальный вызов.
Марк Питерс

Ответы:


44

РЕЗЮМЕ

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

ПОДРОБНОСТИ

За этой ссылкой есть две реализации: 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), которая имеет ленивую оценку. Проблема в том, что он действительно основан на ленивой оценке. Вопрос в том, можно ли его преобразовать в алгоритм 3 стека без ленивых вычислений.

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

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


Я просто пролистал бумаги и программы по вашей ссылке. Но если я правильно понял, они не используют стеки, они используют списки в качестве основного типа. И особенно в этих списках построены заголовок и остальные, поэтому он в основном выглядит как мое решение (и я думаю, что это не правильно).
flolo

1
Привет, реализации на функциональном языке, где списки соответствуют стекам, пока указатели не являются общими, и они не являются; версия с шестью стеками действительно может быть реализована с использованием шести «простых» стеков. Проблема с версией с двумя / тремя стеками состоит в том, что она использует скрытые структуры данных (замыкания).
Антти Хайма

Вы уверены, что решение с шестью стеками не имеет общих указателей? В rotateэто выглядит frontсписок получает назначение в обоих oldfrontи f, и которые затем изменены отдельно.
межджай

14
Исходный материал по адресу algs4.cs.princeton.edu/13stacks был изменен: 43. Реализовать очередь с постоянным числом стеков [не «3»], чтобы каждая операция очереди занимала постоянное (в худшем случае) количество стеков. операции. Предупреждение: высокая степень сложности. Тем не менее, в названии задачи говорится «Очередь с тремя стеками» :-).
Марк Питерс

3
@AnttiHuima Ссылка на шесть стеков мертва, вы знаете, существует ли она где-нибудь?
Квентин Праде

12

Хорошо, это действительно сложно, и единственное решение, которое я мог придумать, помнит меня о решении Киркса для теста Кобаяси Мару (каким-то образом обманутым): Идея в том, что мы используем стек стеков (и используем его для моделирования списка ). Я называю операции 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) в терминах стековых операций.

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

РЕДАКТИРОВАТЬ: Пример объяснения:

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

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

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

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

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


Не могли бы вы объяснить, как это работает? Может быть, проследить нажатие «A», «B», «C», «D» и затем нажать 4 раза?
МАК

1
@Iceman: Нет Stack2 правильно. Они не потеряны, потому что Stack всегда ссылается на самый внутренний стек в Stack1, поэтому они все еще неявны в Stack1.
flolo

3
Я согласен, что это измена :-). Это не 3 стека, а 3 стека. Но приятного чтения.
Марк Питерс

1
Это умная схема, но если я правильно ее понимаю, то в итоге потребуется n стеков, когда в очередь помещено n элементов. Вопрос требует ровно 3 стека.
МАК

2
@ MAK: я знаю, поэтому прямо заявил, что это обмануло (я даже потратил репутацию на награду, потому что мне также любопытно настоящее решение). Но, по крайней мере, на пруссанский комментарий можно ответить: количество стеков важно, потому что мое решение действительно верное, когда вы можете использовать столько, сколько хотите.
flolo

4

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


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

Утверждения

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

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

Пусть элементы, поставленные в очередь в 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)| явно неограничен по п.

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

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

Определить * 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)

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

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

Следовательно,

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

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


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

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

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

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

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

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

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

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

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


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


Т.е.

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

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

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

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

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

И поэтому противоречие.


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

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

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

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


2
Я думаю, что доказательство работает для этих предположений - но я не уверен, что все стеки должны быть пустыми, чтобы очередь была пустой, или что сумма размеров стеков должна равняться размеру очереди.
Mikeb

3
«WLOG1, предположим, что стек A неограничен, а стеки B и C ограничены». Вы не можете предполагать, что некоторые стеки ограничены, так как это сделает их бесполезными (они будут такими же, как O (1) дополнительного хранилища).
межджай

3
Иногда тривиальные вещи не так уж тривиальны: | Q | = | A | + | B | + | C | верно только в том случае, если вы предполагаете, что для каждой записи в Q вы добавляете точную единицу в A, B или C, но может случиться так, что есть некоторый алгоритм, который всегда добавляет элемент два раза в два стека или даже во все три. И если это работает таким образом, вы WLOG1 больше не держите (например, представьте себе C копию A (не то, чтобы это имело смысл, но, возможно, есть алгоритм, с другим порядком или чем-то еще ...)
flolo

@flolo и @mikeb: вы оба правы. | Q (п) | должен быть определен как | A (n) | + | B (n) | + | C (n) |. И тогда | Q (n) | > = н. Впоследствии доказательство будет работать с n, и заметим, что до тех пор, пока | Q (n) | больше, вывод применяется.
Динфэн Кек

@interjay: Вы можете иметь 3 неограниченных стека и не иметь ограниченных стеков. Тогда вместо «B_u + C_u + 1» в доказательстве можно просто использовать «1». По сути, выражение представляет «сумму верхней границы в ограниченных стопках + 1», поэтому количество ограниченных стопок не будет иметь значения.
Динфэн Кек

3

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

@ antti.huima: связанные списки - это не то же самое, что стек.

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

  • s2 = выскочил (s1) --- s2 сейчас (2 3 4)

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

  • s3 = выскочил (выскочил (s1)) --- s3 есть (3 4)

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

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

  • s1 - ссылка на вершину стека
  • s2 - ссылка на второй элемент стека
  • s3 это ссылка на третий ...

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

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

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


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


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


1
Я не могу говорить с другими алгоритмами в ответе Антти, но решение с шестью стеками в постоянном времени ( eecs.usma.edu/webs/people/okasaki/jfp95/queue.hm.sml ) не использует это свойство списков , поскольку я повторно реализовал это в Java, используя java.util.Stackобъекты. Единственное место, где эта функция используется, - это оптимизация, которая позволяет «дублироваться» неизменным стекам в постоянное время, чего не могут сделать базовые стеки Java (но которые могут быть реализованы в Java), поскольку они являются изменяемыми структурами.
Welbog

Если это оптимизация, которая не уменьшает вычислительную сложность, это не должно повлиять на заключение. Рад наконец-то найти решение, а теперь проверить его: но я не люблю читать SML. Не могли бы вы поделиться своим кодом Java? (:
Dingfeng Quek

Это не окончательное решение, поскольку, к сожалению, вместо шести используется шесть стеков. Тем не менее, можно доказать, что шесть стеков - это минимальное решение ...
Welbog

@Welbog! Можете ли вы поделиться своей 6-стековой реализацией? :) Было бы круто увидеть это :)
Антти Хайма

1

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

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

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

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

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


Что касается первоначального вопроса: для разбиения стека требуется дополнительный промежуточный стек. Возможно, поэтому ваша задача включала три стека.
Томас Ахле
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.