Як реалізувати чергу з трьома стеками?


136

Я натрапив на це питання в книзі алгоритмів (" Алгоритми", 4-е видання Роберта Седжевіка та Кевіна Вейна).

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

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

(о, і це не домашнє завдання :))


30
Я думаю, що це варіант Ханойської вежі .
Gumbo

14
@Jason: Це питання не є дублікатом, оскільки він вимагає амортизованого часу O (1), тоді як цей запитує O (1) найгірший для кожної операції. Дво стек-рішення DuoSRX - це вже амортизований час O (1) за операцію.
interjay

15
Автор впевнений, що не жартував, сказав: "Попередження: висока ступінь складності".
BoltClock

9
@Gumbo, на жаль, складність у часі Ханойської вежі ніде не є майже постійним часом!
prusswan

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

Відповіді:


44

ПІДСУМОК

  • Алгоритм O (1) відомий для 6 стеків
  • Алгоритм O (1) відомий для 3 стеків, але використовуючи ледачу оцінку, яка на практиці відповідає наявності додаткових внутрішніх структур даних, тому це не є рішенням
  • Люди поблизу Sedgewick підтвердили, що не знають про 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-стековий алгоритм без лінивої оцінки.

ОНОВЛЕННЯ: Інший пов'язаний алгоритм описаний у статті "Стеки проти деків" Холгера Петерсена, опублікованій у 7-й щорічній конференції з обчислень та комбінаторики. Ви можете знайти статтю з Книг Google. Перевірте сторінки 225-226. Але алгоритм насправді не є моделюванням у режимі реального часу, це лінійним моделюванням подвійної черги у трьох стеках.

gusbro: "Як казав @Leonel кілька днів тому, я вважав, що було б чесно перевірити з професором Седжевіком, чи знає він рішення, чи є якась помилка. Тому я йому писав. Щойно я отримав відповідь (хоч і не від сам, але від колеги в Принстоні), тому я люблю ділитися з усіма вами. Він, в основному, сказав, що він не знає алгоритмів, використовуючи три стеки, а інші обмеження, накладені (як, наприклад, не використовуючи ледачу оцінку). Він знав про алгоритм використання 6 стеків, як ми вже знаємо, дивлячись на відповіді тут. Тому я думаю, що питання все ще відкрито, щоб знайти алгоритм (або довести, що його неможливо знайти). "


Я щойно перелітав папери та програми у вашому посиланні. Але якщо я бачу правильно, вони не використовують стеки, вони використовують списки як базовий тип. І особливо. у цих списках побудовано заголовок та решта, тому в основному це схоже на моє рішення (і я вважаю, що це неправильно).
флоло

1
Привіт, реалізації реалізовані на функціональній мові, де списки відповідають стекам до тих пір, поки покажчики не поділяються, і їх немає; версія шести стека може бути реально реалізована за допомогою шести "простих" стеків. Проблема версії двох / трьох стеків полягає в тому, що вона використовує приховані структури даних (закриття).
Antti Huima

Ви впевнені, що рішення шести стека не має вказівників? Здається rotate, що frontсписок присвоюється обом oldfrontі f, і вони потім змінюються окремо.
interjay

14
Вихідний матеріал у algs4.cs.princeton.edu/13stacks змінено: 43. Встановіть чергу з постійною кількістю стеків [not "3"], щоб кожна операція черги приймала постійну (найгірше) кількість стека операції. Попередження: висока ступінь складності. У назві виклику все ще написано "Черга з трьома стеками", проте :-).
Марк Петерс

3
@AnttiHuima Шість стеків посилання мертва, чи знаєте ви, чи це десь існує?
Квентін Прадет

12

Добре, це справді важко, і єдине рішення, яке я міг би придумати, пам’ятає мене про рішення Кіркса для тесту Кобаяші Мару (якось обдурене): Ідея полягає в тому, що ми використовуємо стек стеків (і використовуємо це для моделювання списку ). Я називаю операції en / dequeue, натискаю і клацаю, тоді ми отримуємо:

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.

Кожен елемент загортається в єдиний стек елементів (тому у нас є лише typesafe стек стеків).

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

Esp складно - це вставка, оскільки вам доведеться якось зануритися вглиб вкладених стеків, щоб додати ще один елемент. Ось чому Stack2 там. Stack2 - це завжди внутрішній стек. Потім додавання - це просто просунення елемента, а потім натискання на новий Stack2 (і саме тому нам не дозволяють торкатися Stack2 в нашій операції з dequeue).


Чи хотіли б ви пояснити, як це працює? Можливо, простежте, натиснувши "A", "B", "C", "D", а потім вискакуючи 4 рази?
MAK

1
@Iceman: Немає стека2. Вони не втрачаються, тому що Stack завжди посилається на найпотаємніший стек у Stack1, тому вони все ще неявні в Stack1.
флоло

3
Я згоден, що це обман :-). Це не 3 стеки, це 3 посилання на стеки. Але приємне читання.
Марк Петерс

1
Це розумна схема, але якщо я правильно це зрозумів, в кінцевому підсумку знадобиться n стеків, коли до черги буде п ять елементів. Питання задає рівно 3 стеки.
МАК

2
@MAK: Я знаю, ось чому явно сказано, що його обдурили (я навіть витратив репутацію на винагороду, тому що мені також цікаво реальне рішення). Але принаймні на коментар prusswan можна відповісти: Кількість стеків важлива, тому що моє рішення справді є дійсним, коли ви можете використовувати скільки завгодно.
flolo

4

Я спробую довести, щоб показати, що цього не можна зробити.


Припустимо, існує черга Q, яка імітується 3 стеками, A, B і C.

Твердження

  • ASRT0: = Крім того, припустимо, що Q може імітувати операції {черга, dequeue} в 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)|не пов'язаний з n.

WLOG1, припустимо, стек A не є обмеженим, а стеки B і C обмежені.

Визначте * B_u :=верхню межу B * C_u :=верхню межу C *K := B_u + C_u + 1

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

  • X := цей елемент

Тоді ми можемо визначитись

  • Abv(n) :=кількість елементів у стеку, A(n)що перевищує X
  • Blo(n) :=кількість елементів у стеці, A(n)що нижче X

    | A (n) | = Abv (n) + Blo (n)

ASRT1 :=Кількість спливів, необхідних для зняття з Х, Q(n)становить щонайменшеAbv(n)

З ( 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) := цей елемент, для будь-якого даного 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)є необмеженим на n.

Тому кількість спливів, необхідних для того, щоб поставити елемент нижче, X(n)не обмежується.


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


Тобто

Принаймні 1 стек повинен бути необмеженим.

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

Однак якщо кількість елементів над нею обмежена, то вона досягне межі. У якийсь момент під ним повинен ввести новий елемент.

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

Щоб ввести новий елемент під ним, оскільки над ним є необмежена кількість елементів, для необмеженої кількості елементів потрібна необмежена кількість спливаючих елементів.

І таким чином суперечність.


Є 5 тверджень WLOG (Без втрати загальності). У певному сенсі їх можна інтуїтивно зрозуміти як істинні (але враховуючи, що їх 5, це може зайняти деякий час). Офіційний доказ того, що жодна загальність не втрачається, може бути отриманий, але є надзвичайно тривалим. Вони опущені.

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

Третя стека також не має значення. Важливо те, що існує набір обмежених стеків і безліч необмежених стеків. Мінімум, необхідний для прикладу, - 2 стеки. Кількість штабелів повинна бути, звичайно, кінцевою.

Нарешті, якщо я маю рацію, що немає доказів, тоді повинен бути простіший індуктивний доказ. Можливо, виходячи з того, що відбувається після кожної черги (слідкуйте за тим, як це впливає на найгірший випадок декуе, враховуючи набір усіх елементів у черзі).


2
Я думаю, що доказ працює для цих припущень - але я не впевнений, що всі стеки повинні бути порожніми, щоб черга була порожньою, або що сума розмірів стеків повинна дорівнювати розміру черги.
Майк

3
"WLOG1, припустимо, стек A не обмежений, а стеки B і C обмежені." Ви не можете припустити, що деякі стеки обмежені, оскільки це зробило б їх нікчемними (вони були б такими ж, як O (1) додаткове зберігання).
interjay

3
Іноді банальні речі не такі вже й дрібниці: | Q | = | А | + | В | + | С | це правильно, якщо ви припускаєте, що для кожного запису в Q ви додаєте точне в A, B або C, але може бути, що theer - це якийсь алгоритм, який завжди додає елемент два рази до двох стеків або навіть усіх трьох. І якщо це працює таким чином, ви WLOG1 більше не тримаєте (наприклад, уявіть собі C копію A (не те, що це має сенс, але, можливо, є алгоритм, з іншим порядком чи будь-яким іншим ...)
flolo

@flolo та @mikeb: Ви обоє праві. | Q (n) | слід визначити як | A (n) | + | B (n) | + | C (n) |. І тоді | Q (n) | > = n. Згодом доказ працюватиме з n та зауважимо, що до тих пір, поки | Q (n) | більше, застосовується висновок.
Dingfeng Quek

@interjay: Ви можете мати без обмежених стеків і без обмежених стеків. Тоді замість "B_u + C_u + 1", доказ може просто використовувати "1". В основному, вираз представляє "суму верхньої межі в обмежених стеках + 1", тому кількість обмежених стеків не має значення.
Дінгфенг Квек

3

Примітка. Це означає, що це коментар до функціональної реалізації черг у реальному часі (у найгіршому випадку з постійним часом) із окремо пов'язаними списками. Я не можу додавати коментарі через репутацію, але буде добре, якщо хтось міг би змінити це на коментар, доданий до відповіді 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 є (3 4)

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

Що це означає?

  • s1 - посилання на верхню частину стека
  • s2 - посилання на другий елемент стека
  • s3 - посилання на третій ...

Створені зараз додаткові пов'язані списки служать в якості посилання / покажчика! Кінцева кількість стеків не може цього зробити.

З того, що я бачу в документах / кодах, всі алгоритми використовують це властивість пов'язаних списків для збереження посилань.

Редагувати: Я маю на увазі лише алгоритми 2 та 3 зв'язаного списку, використовуючи цю властивість пов'язаних списків, як я їх прочитав спочатку (вони виглядали простішими). Це не має на меті показати, що вони є чи не застосовуються, а лише пояснити, що пов'язані списки не обов'язково однакові. Я прочитаю одну з 6, коли я вільний. @Welbog: Дякую за виправлення.


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


Використання пов'язаного списку вирішує іншу проблему. Ця стратегія може бути використана для реалізації черг у режимі реального часу в Lisp (Або принаймні Lisps, які наполягають на створенні всього із пов'язаних списків). Це також хороший спосіб скласти незмінні списки з часом роботи O (1) та спільними (незмінними) структурами.


1
Я не можу говорити з іншими алгоритмами у відповіді antti, але шести стек-рішення постійного часу ( eecs.usma.edu/webs/people/okasaki/jfp95/queue.hm.sml ) не використовує цю властивість списків , оскільки я повторно реалізував це в Java за допомогою java.util.Stackоб'єктів. Єдине місце, де використовується ця функція, - це оптимізація, яка дозволяє незмінним стекам "дублюватися" протягом постійного часу, що базові стеки Java не можуть робити (але які можуть бути реалізовані в Java), оскільки вони є змінними структурами.
Welbog

Якщо це оптимізація, яка не зменшує складність обчислень, це не повинно впливати на висновок. Радий, що нарешті є рішення, тепер перевірити його: Але мені не подобається читати SML. Ви хочете поділитися своїм Java-кодом? (:
Дінгфенг Кек

Це, на жаль, не остаточне рішення, оскільки він використовує шість стеків замість трьох, на жаль. Однак, можливо, вдасться довести, що шість стеків - це мінімальне рішення ...
Welbog

@Welbog! Чи можете ви поділитися своєю 6-стековою реалізацією? :) Було б здорово це побачити :)
Antti Huima

1

Ви можете зробити це в амортизованому постійному часі з двома стеками:

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

Додавання O(1)та видалення - це O(1)якщо сторона, яку ви хочете взяти, не порожня іO(n) інакше (розділіть інший стек надвоє).

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

Хоча це може виглядати як проблема, якщо ви використовуєте імперативну мову зі стеком на основі масиву (найшвидший), у будь-якому випадку вам доведеться мати лише постійний амортизований час.


Щодо початкового питання: Розщеплення стека насправді вимагає додаткового проміжного стеку. Ось чому ваша задача включала три стеки.
Thomas Ahle
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.