Як реалізувати чергу за допомогою двох стеків?


394

Припустимо, у нас є два стеки та жодна інша тимчасова змінна.

Чи можливо "побудувати" структуру даних черги, використовуючи лише два стеки?

Відповіді:


701

Зберігайте 2 стеки, давайте назвемо їх inboxі outbox.

Запитання :

  • Натисніть на новий елемент inbox

Dequeue :

  • Якщо outboxпорожній, поповніть його, вискакуючи з нього кожен елемент inboxі натисніть на ньогоoutbox

  • Розмістіть і поверніть верхній елемент з outbox

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

Ось реалізація на Java:

public class Queue<E>
{

    private Stack<E> inbox = new Stack<E>();
    private Stack<E> outbox = new Stack<E>();

    public void queue(E item) {
        inbox.push(item);
    }

    public E dequeue() {
        if (outbox.isEmpty()) {
            while (!inbox.isEmpty()) {
               outbox.push(inbox.pop());
            }
        }
        return outbox.pop();
    }

}

13
Найгірша часова складність все ще залишається O (n). Я наполегливо кажу це, тому що я сподіваюся, що жоден студент там (це звучить як домашнє завдання / навчальне запитання) не вважає, що це прийнятний спосіб запровадити чергу.
Тайлер

26
Це правда, що найгірший час для однієї операції попу - O (n) (де n - поточний розмір черги). Однак найгіршим часом для послідовності n операцій черги є також O (n), що дає нам постійний амортизований час. Я б не реалізував цю чергу таким чином, але це не так вже й погано.
Дейв Л.

1
@Tyler Якщо ваш стек заснований на масиві, як і більшість, ви завжди отримаєте O (n) найгірший випадок за одну операцію.
Томас Ейл

2
@Tyler: Перевірте sgi.com/tech/stl/Deque.html . Деке "підтримує випадковий доступ до елементів". Отже, і deque, і стек засновані на масиві. Це тому, що це дає вам кращу орієнтацію та, отже, швидше на практиці.
Томас Ейл

13
@Newtang a) черга 1,2,3 => Вхідні [3,2,1] / Вихідні [] . б) декуе. папка "Вихідні" порожня, тому поповнюйте => Вхідні [] / Вихідні [1,2,3] . Вийдіть із вихідної пошти , поверніть 1 => Вхідні [] / Вихідні [2,3] . в) черга 4,5 => Вхідні [5,4] / Вихідні [2,3] . г) декуе. папка "Вихідні" не порожня, тому вийдіть із вихідних, поверніть 2 => Вхідні [5,4] / Вихідні [3] . Це має більше сенсу?
Дейв Л.

226

A - Як повернути стек

Щоб зрозуміти, як побудувати чергу за допомогою двох стеків, слід зрозуміти, як повернути стек кришталево. Пам’ятайте, як працює стек, він дуже схожий на стек посуду на вашій кухні. Останнє вимите посуд опиниться у верхній частині чистої стопки, яку називають L ast I n F irst O в інформатиці ut (LIFO).

Давайте уявимо нашу стопку, як пляшку, як нижче;

введіть тут опис зображення

Якщо ми натиснемо цілі числа 1,2,3 відповідно, то 3 буде на вершині стека. Оскільки 1 буде натиснуто спочатку, то 2 буде поставлено на вершину 1. Нарешті, 3 буде розміщено на вершині стопки, а останнє стан нашого стека, представлене як пляшка, буде як нижче;

введіть тут опис зображення

Тепер у нас стек представлений як пляшка заповнена значеннями 3,2,1. І ми хочемо повернути стек так, щоб верхній елемент стека був 1, а нижній елемент стека був 3. Що ми можемо зробити? Ми можемо взяти пляшку і потримати її догори дном, щоб усі значення повинні були змінитись у порядку?

введіть тут опис зображення

Так, ми можемо це зробити, але це пляшка. Для того ж процесу нам потрібно мати другий стек, який буде зберігати перші елементи стека у зворотному порядку. Покладемо наш заселений стек зліва і наш новий порожній стек праворуч. Щоб змінити порядок елементів, ми збираємося виводити кожен елемент з лівого стека та переміщувати їх у правий стек. Ви можете бачити, що відбувається, як ми робимо це, на зображенні нижче;

введіть тут опис зображення

Тож ми знаємо, як повернути стек.

В - Використання двох стеків як черги

У попередній частині я пояснив, як можна змінити порядок елементів стеку. Це було важливо, тому що якщо ми натиснемо та виведемо елементи до стеку, вихід буде точно у зворотному порядку черги. Розмірковуючи про приклад, давайте підсунемо масив цілих чисел {1, 2, 3, 4, 5}до стека. Якщо ми спливемо елементи та надрукуємо їх до тих пір, поки стек не буде порожнім, ми отримаємо масив у зворотному порядку поштовхового порядку, який буде {5, 4, 3, 2, 1}пам'ятати, що для того ж входу, якщо ми відкладемо чергу, поки черга не буде порожньою, вихід буде{1, 2, 3, 4, 5} . Тож очевидно, що для одного і того ж порядку введення елементів вихід черги є точно зворотним до виводу стека. Оскільки ми знаємо, як повернути стек, використовуючи додатковий стек, ми можемо побудувати чергу, використовуючи дві стеки.

Наша модель черги буде складатися з двох стеків. Один стек буде використовуватися для enqueueроботи (стек №1 зліва, буде називатися вхідним стеком), інший стек буде використаний для dequeueоперації (стек №2 справа, буде називатися вихідним стеком). Перегляньте зображення нижче;

введіть тут опис зображення

Наш псевдокод наведений нижче;


Операція допит

Push every input element to the Input Stack

Операція Dequeue

If ( Output Stack is Empty)
    pop every element in the Input Stack
    and push them to the Output Stack until Input Stack is Empty

pop from Output Stack

Зробимо {1, 2, 3}відповідно цілі числа . Цілі особи будуть висунуті на стек вводу ( стек №1 ), який знаходиться зліва;

введіть тут опис зображення

Тоді, що буде, якщо ми виконаємо операцію dequue? Щоразу, коли виконується операція декерування, черга збирається перевірити, чи стек виводу порожній чи ні (див. Псевдо-код вище). Якщо стек вихідного сигналу порожній, тоді стек вводу буде вилучений на виході, так що елементи стека вводу буде повернено. Перед поверненням значення стан черги буде таким, як нижче;

введіть тут опис зображення

Перевірте порядок елементів у стеку виводу (стек №2). Очевидно, що ми можемо спливати елементи з вихідного стеку, щоб результат був таким самим, як якщо б ми вийшли з черги. Таким чином, якщо ми виконаємо дві операції dequeue, спочатку отримаємо {1, 2}відповідно. Тоді елемент 3 буде єдиним елементом стеку виводу, а стек вводу буде порожнім. Якщо ми зав'яжемо елементи 4 та 5, то стан черги буде таким;

введіть тут опис зображення

Тепер стек вихідного сигналу не порожній, і якщо ми виконаємо операцію з видалення, із стеку виводу буде виведено лише 3. Тоді стан буде розглядатися як нижче;

введіть тут опис зображення

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

введіть тут опис зображення

Легко помітити, вихід двох операцій з декеюванням буде {4, 5}

C - реалізація черги, побудованої з двох стеків

Ось реалізація в Java. Я не збираюся використовувати існуючу реалізацію Stack, тому приклад тут буде винаходити колесо;

C - 1) Клас MyStack: Проста реалізація стека

public class MyStack<T> {

    // inner generic Node class
    private class Node<T> {
        T data;
        Node<T> next;

        public Node(T data) {
            this.data = data;
        }
    }

    private Node<T> head;
    private int size;

    public void push(T e) {
        Node<T> newElem = new Node(e);

        if(head == null) {
            head = newElem;
        } else {
            newElem.next = head;
            head = newElem;     // new elem on the top of the stack
        }

        size++;
    }

    public T pop() {
        if(head == null)
            return null;

        T elem = head.data;
        head = head.next;   // top of the stack is head.next

        size--;

        return elem;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void printStack() {
        System.out.print("Stack: ");

        if(size == 0)
            System.out.print("Empty !");
        else
            for(Node<T> temp = head; temp != null; temp = temp.next)
                System.out.printf("%s ", temp.data);

        System.out.printf("\n");
    }
}

C - 2) Клас MyQueue: Реалізація черги за допомогою двох стеків

public class MyQueue<T> {

    private MyStack<T> inputStack;      // for enqueue
    private MyStack<T> outputStack;     // for dequeue
    private int size;

    public MyQueue() {
        inputStack = new MyStack<>();
        outputStack = new MyStack<>();
    }

    public void enqueue(T e) {
        inputStack.push(e);
        size++;
    }

    public T dequeue() {
        // fill out all the Input if output stack is empty
        if(outputStack.isEmpty())
            while(!inputStack.isEmpty())
                outputStack.push(inputStack.pop());

        T temp = null;
        if(!outputStack.isEmpty()) {
            temp = outputStack.pop();
            size--;
        }

        return temp;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

}

C - 3) Демо-код

public class TestMyQueue {

    public static void main(String[] args) {
        MyQueue<Integer> queue = new MyQueue<>();

        // enqueue integers 1..3
        for(int i = 1; i <= 3; i++)
            queue.enqueue(i);

        // execute 2 dequeue operations 
        for(int i = 0; i < 2; i++)
            System.out.println("Dequeued: " + queue.dequeue());

        // enqueue integers 4..5
        for(int i = 4; i <= 5; i++)
            queue.enqueue(i);

        // dequeue the rest
        while(!queue.isEmpty())
            System.out.println("Dequeued: " + queue.dequeue());
    }

}

C - 4) Вибірка вибірки

Dequeued: 1
Dequeued: 2
Dequeued: 3
Dequeued: 4
Dequeued: 5

18
Я б поставив +1 цьому цілий день, якби міг. Я не міг зрозуміти, як це амортизується постійний час. Ваші ілюстрації дійсно очистили речі, особливо частину залишення решти елементів на вихідному стеку, і лише заправку, коли вона спорожняється.
Шейн МакКійлан

1
Це дійсно допомогло запобігти помилкам тайм-ауту, які я отримував під час попу. Я розміщував елементи ще в оригінальному стеку, але в цьому не було потреби. Кудо!
Праніт Банкар

2
Усі коментарі слід моделювати після цього.
lolololol ol

4
Мені справді не було потрібного рішення для цього, просто переглядаючи ... Але коли я бачу таку відповідь, я просто закохуюсь .. Чудова відповідь !!!
Маверик

80

Ви навіть можете імітувати чергу, використовуючи лише один стек. Другий (тимчасовий) стек може бути імітований стеком викликів рекурсивних викликів методом вставки.

Принцип залишається таким же, коли вставляєте новий елемент у чергу:

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

Клас черги, що використовує лише один стек, буде таким:

public class SimulatedQueue<E> {
    private java.util.Stack<E> stack = new java.util.Stack<E>();

    public void insert(E elem) {
        if (!stack.empty()) {
            E topElem = stack.pop();
            insert(elem);
            stack.push(topElem);
        }
        else
            stack.push(elem);
    }

    public E remove() {
        return stack.pop();
    }
}

51
Можливо, код виглядає елегантно, але він дуже неефективний (O (n ** 2) вставка), і він все ще має два стеки, один у купі та один у стеку викликів, як вказує @pythonquick. Для нерекурсивного алгоритму завжди можна схопити один "додатковий" стек із стека виклику мовами, що підтримують рекурсію.
Antti Huima

1
@ antti.huima А ви б пояснили, як це може бути квадратична вставка ?! З того, що я розумію, вставка робить n поп і n push операцій, тож це ідеально лінійний алгоритм O (n).
LP_

1
@LP_, щоб вставити n itemsу чергу, використовуючи вищевказану структуру даних, потрібен квадратичний час O (n ^ 2) . сума (1 + 2 + 4 + 8 + .... + 2(n-1))результатів в ~O(n^2). Я сподіваюся, що ви зрозумієте, що
Анкіт Кумар

1
@ antti.huima Ви говорили про складність функції вставки (ви сказали "O (n 2) insert" - ви, мабуть, мали на увазі "O (n 2) fill"). За умовою , "вкладка складності" - це час одного вставки, який тут лінійний за кількістю вже присутніх елементів. Якби ми говорили в час, необхідний для вставки n елементів, ми б сказали, що хештел має лінійну вставку. Що не так.
LP_

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

11

Однак складності в часі були б гіршими. Хороша реалізація черги робить все в постійний час.

Редагувати

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

Трохи детальніше : чому використання двох стеків гірше, ніж просто черга: якщо ви використовуєте дві стеки, а хтось дзвонить у черзі, поки вихідна скринька порожня, вам потрібен лінійний час, щоб дістатися до нижньої частини папки "Вхідні" в коді Дейва).

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


Не в середньому випадку. Відповідь Брайана описує чергу, яка б амортизувала постійні операції з запитуваннями та декеюванням.
Даніель Шпієк

Це правда. У вас середня кількість випадків і амортизована складність у часі однакова. Але за замовчуванням зазвичай це найгірший варіант операції, і це O (n), де n - поточний розмір структури.
Тайлер

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

1
"Найгірший" і "амортизований" - це два різних типи часової складності. Немає сенсу говорити, що "найгірший випадок може бути амортизований" - якщо ви могли б зробити найгірший випадок = амортизований, то це було б значним поліпшенням; ви б просто поговорили про найгірший випадок, не маючи середнього значення.
Тайлер

Я не впевнений, що ви маєте на увазі під тим, що O (1) найгірший випадок "суворо кращий", ніж комбінація O (1) середнього випадку та O (n) найгіршого випадку. Постійні фактори масштабування мають значення. Структура даних, яка, якщо вона містить N елементів, може потребувати повторної упаковки після N операцій за час N мікросекунд, інакше займає одну мікросекундну операцію, може бути набагато кориснішою, ніж та, яка займає мілісекунд для кожної операції, навіть якщо розмір даних розшириться до мільйонів елементів (це означає, що деякі окремі операції потребуватимуть декількох секунд).
supercat

8

Нехай чергою, яку потрібно реалізувати, є q, а стеки, які використовуються для реалізації q, будуть stack1 та stack2.

q може бути реалізовано двома способами:

Спосіб 1 (Дорога операція enQueue)

Цей метод гарантує, що щойно введений елемент завжди знаходиться у верхній частині стека 1, так що операція deQueue просто вискакує зі стека1. Для розміщення елемента в вершині стека1 використовується stack2.

enQueue(q, x)
1) While stack1 is not empty, push everything from stack1 to stack2.
2) Push x to stack1 (assuming size of stacks is unlimited).
3) Push everything back to stack1.
deQueue(q)
1) If stack1 is empty then error
2) Pop an item from stack1 and return it.

Спосіб 2 (Дорога операція deQueue)

У цьому методі при операції з чергою новий елемент вводиться у верхній частині стека1. У режимі де-черги, якщо stack2 порожній, всі елементи переміщуються в stack2 і, нарешті, повертається вершина stack2.

enQueue(q,  x)
 1) Push x to stack1 (assuming size of stacks is unlimited).

deQueue(q)
 1) If both stacks are empty then error.
 2) If stack2 is empty
   While stack1 is not empty, push everything from stack1 to stack2.
 3) Pop the element from stack2 and return it.

Спосіб 2, безумовно, кращий, ніж метод 1. Метод 1 переміщує всі елементи двічі під час роботи enQueue, тоді як метод 2 (в операції deQueue) переміщує елементи один раз і переміщує елементи лише у тому випадку, якщо stack2 порожній.


Жодне з рішень я не зрозумів, окрім вашого способу 2. Мені подобається, як ви пояснюєте це методом enqueue та dequeue за допомогою етапів.
theGreenCabbage


3

Рішення в c #

public class Queue<T> where T : class
{
    private Stack<T> input = new Stack<T>();
    private Stack<T> output = new Stack<T>();
    public void Enqueue(T t)
    {
        input.Push(t);
    }

    public T Dequeue()
    {
        if (output.Count == 0)
        {
            while (input.Count != 0)
            {
                output.Push(input.Pop());
            }
        }

        return output.Pop();
    }
}

2

Два стеки у черзі визначаються як stack1 та stack2 .

Enqueue: Евкейовані елементи завжди висуваються в стек1

Ведіеій: Вершина stack2 може бути вискочило , так як це перший елемент вставляється в чергу , коли stack2 не пустили. Коли stack2 порожній, ми вискакуємо всі елементи з stack1 і штовхаємо їх у stack2 один за одним. Перший елемент черги висувається в нижню частину стека1 . Він може вискочити безпосередньо після операцій з виштовхуванням та натисканням, оскільки він знаходиться у верхній частині стека2 .

Нижче наведений той самий код вибірки C ++:

template <typename T> class CQueue
{
public:
    CQueue(void);
    ~CQueue(void);

    void appendTail(const T& node); 
    T deleteHead();                 

private:
    stack<T> stack1;
    stack<T> stack2;
};

template<typename T> void CQueue<T>::appendTail(const T& element) {
    stack1.push(element);
} 

template<typename T> T CQueue<T>::deleteHead() {
    if(stack2.size()<= 0) {
        while(stack1.size()>0) {
            T& data = stack1.top();
            stack1.pop();
            stack2.push(data);
        }
    }


    if(stack2.size() == 0)
        throw new exception("queue is empty");


    T head = stack2.top();
    stack2.pop();


    return head;
}

Це рішення запозичено з мого блогу . Більш детальний аналіз з покроковими моделюваннями роботи доступний на веб-сторінці мого блогу.


2

Вам доведеться вискакувати все з першого стеку, щоб отримати нижній елемент. Потім покладіть їх назад на другий стек для кожної операції "dequeue".


3
Так, ти правий. Цікаво, як у вас так багато голосів. Я відхилив вашу відповідь
Бініта Бхараті

Моторошно бачити, що це була його остання відповідь і відтоді минуло десятиліття.
Шану Гупта

2

для розробника c # ось повна програма:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QueueImplimentationUsingStack
{
    class Program
    {
        public class Stack<T>
        {
            public int size;
            public Node<T> head;
            public void Push(T data)
            {
                Node<T> node = new Node<T>();
                node.data = data;
                if (head == null)
                    head = node;
                else
                {
                    node.link = head;
                    head = node;
                }
                size++;
                Display();
            }
            public Node<T> Pop()
            {
                if (head == null)
                    return null;
                else
                {
                    Node<T> temp = head;
                    //temp.link = null;
                    head = head.link;
                    size--;
                    Display();
                    return temp;
                }
            }
            public void Display()
            {
                if (size == 0)
                    Console.WriteLine("Empty");
                else
                {
                    Console.Clear();
                    Node<T> temp = head;
                    while (temp!= null)
                    {
                        Console.WriteLine(temp.data);
                        temp = temp.link;
                    }
                }
            }
        }

        public class Queue<T>
        {
            public int size;
            public Stack<T> inbox;
            public Stack<T> outbox;
            public Queue()
            {
                inbox = new Stack<T>();
                outbox = new Stack<T>();
            }
            public void EnQueue(T data)
            {
                inbox.Push(data);
                size++;
            }
            public Node<T> DeQueue()
            {
                if (outbox.size == 0)
                {
                    while (inbox.size != 0)
                    {
                        outbox.Push(inbox.Pop().data);
                    }
                }
                Node<T> temp = new Node<T>();
                if (outbox.size != 0)
                {
                    temp = outbox.Pop();
                    size--;
                }
                return temp;
            }

        }
        public class Node<T>
        {
            public T data;
            public Node<T> link;
        }

        static void Main(string[] args)
        {
            Queue<int> q = new Queue<int>();
            for (int i = 1; i <= 3; i++)
                q.EnQueue(i);
           // q.Display();
            for (int i = 1; i < 3; i++)
                q.DeQueue();
            //q.Display();
            Console.ReadKey();
        }
    }
}

2

Виконайте наступні операції черги, використовуючи стеки.

push (x) - Натисніть на елемент x задній частині черги.

pop () - видаляє елемент перед чергою.

peek () - Отримайте передній елемент.

empty () - Повертає чи порожня черга.

введіть тут опис зображення

class MyQueue {

  Stack<Integer> input;
  Stack<Integer> output;

  /** Initialize your data structure here. */
  public MyQueue() {
    input = new Stack<Integer>();
    output = new Stack<Integer>();
  }

  /** Push element x to the back of queue. */
  public void push(int x) {
    input.push(x);
  }

  /** Removes the element from in front of queue and returns that element. */
  public int pop() {
    peek();
    return output.pop();
  }

  /** Get the front element. */
  public int peek() {
    if(output.isEmpty()) {
        while(!input.isEmpty()) {
            output.push(input.pop());
        }
    }
    return output.peek();
  }

  /** Returns whether the queue is empty. */
  public boolean empty() {
    return input.isEmpty() && output.isEmpty();
  }
}

1
// Two stacks s1 Original and s2 as Temp one
    private Stack<Integer> s1 = new Stack<Integer>();
    private Stack<Integer> s2 = new Stack<Integer>();

    /*
     * Here we insert the data into the stack and if data all ready exist on
     * stack than we copy the entire stack s1 to s2 recursively and push the new
     * element data onto s1 and than again recursively call the s2 to pop on s1.
     * 
     * Note here we can use either way ie We can keep pushing on s1 and than
     * while popping we can remove the first element from s2 by copying
     * recursively the data and removing the first index element.
     */
    public void insert( int data )
    {
        if( s1.size() == 0 )
        {
            s1.push( data );
        }
        else
        {
            while( !s1.isEmpty() )
            {
                s2.push( s1.pop() );
            }
            s1.push( data );
            while( !s2.isEmpty() )
            {
                s1.push( s2.pop() );
            }
        }
    }

    public void remove()
    {
        if( s1.isEmpty() )
        {
            System.out.println( "Empty" );
        }
        else
        {
            s1.pop();

        }
    }

1

Реалізація черги з використанням двох стеків у Swift:

struct Stack<Element> {
    var items = [Element]()

    var count : Int {
        return items.count
    }

    mutating func push(_ item: Element) {
        items.append(item)
    }

    mutating func pop() -> Element? {
        return items.removeLast()
    }

    func peek() -> Element? {
        return items.last
    }
}

struct Queue<Element> {
    var inStack = Stack<Element>()
    var outStack = Stack<Element>()

    mutating func enqueue(_ item: Element) {
        inStack.push(item)
    }

    mutating func dequeue() -> Element? {
        fillOutStack() 
        return outStack.pop()
    }

    mutating func peek() -> Element? {
        fillOutStack()
        return outStack.peek()
    }

    private mutating func fillOutStack() {
        if outStack.count == 0 {
            while inStack.count != 0 {
                outStack.push(inStack.pop()!)
            }
        }
    }
}

1

Хоча ви отримаєте багато публікацій, пов’язаних із впровадженням черги з двома стеками: 1. Або зробивши процес enQueue набагато дорожчим 2. Або зробивши процес deQueue набагато дорожчим

https://www.geeksforgeeks.org/queue-using-stacks/

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

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

Нижче наведено пояснення проблеми:

  1. Оголосіть єдиний стек для введення в дію черг і відключення даних і висуньте їх у стек.

  2. в той час як deQueueing мають базову умову, коли елемент стека з'являється, коли розмір стека дорівнює 1. Це забезпечить відсутність переповнення стеку під час рекурсії deQueue.

  3. Під час deQueueing спочатку виводите дані з верхньої частини стека. В ідеалі цей елемент буде тим елементом, який присутній у верхній частині стека. Тепер, коли це зроблено, рекурсивно викликайте функцію deQueue, а потім штовхайте елемент, що вискочив вище, назад у стек.

Код буде виглядати нижче:

if (s1.isEmpty())
System.out.println("The Queue is empty");
        else if (s1.size() == 1)
            return s1.pop();
        else {
            int x = s1.pop();
            int result = deQueue();
            s1.push(x);
            return result;

Таким чином ви можете створити чергу, використовуючи структуру даних одного стека та стек виклику рекурсії.


1

Нижче наведено рішення мовою javascript з використанням синтаксису ES6.

Stack.js

//stack using array
class Stack {
  constructor() {
    this.data = [];
  }

  push(data) {
    this.data.push(data);
  }

  pop() {
    return this.data.pop();
  }

  peek() {
    return this.data[this.data.length - 1];
  }

  size(){
    return this.data.length;
  }
}

export { Stack };

QueueUsingTwoStacks.js

import { Stack } from "./Stack";

class QueueUsingTwoStacks {
  constructor() {
    this.stack1 = new Stack();
    this.stack2 = new Stack();
  }

  enqueue(data) {
    this.stack1.push(data);
  }

  dequeue() {
    //if both stacks are empty, return undefined
    if (this.stack1.size() === 0 && this.stack2.size() === 0)
      return undefined;

    //if stack2 is empty, pop all elements from stack1 to stack2 till stack1 is empty
    if (this.stack2.size() === 0) {
      while (this.stack1.size() !== 0) {
        this.stack2.push(this.stack1.pop());
      }
    }

    //pop and return the element from stack 2
    return this.stack2.pop();
  }
}

export { QueueUsingTwoStacks };

Нижче наведено використання:

index.js

import { StackUsingTwoQueues } from './StackUsingTwoQueues';

let que = new QueueUsingTwoStacks();
que.enqueue("A");
que.enqueue("B");
que.enqueue("C");

console.log(que.dequeue());  //output: "A"

Це помилка. Якщо після нанесення черги ви додасте більше елементів, ви вкладете їх stack1. Коли ви dequeueзнову переходите до них , ви переміщуватимете в них предмети stack2, висуваючи їх перед тим, що вже було.
Олександр -

0

Я відповім на це запитання у Go, тому що Go не має багато багато колекцій у своїй стандартній бібліотеці.

Оскільки стек реально легко реалізувати, я подумав, що спробую використати два стеки, щоб виконати подвійну чергу. Щоб краще зрозуміти, як я дійшов до своєї відповіді, я розділив реалізацію на дві частини, сподіваємось, першу частину простіше зрозуміти, але вона неповна.

type IntQueue struct {
    front       []int
    back        []int
}

func (q *IntQueue) PushFront(v int) {
    q.front = append(q.front, v)
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[0]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        q.back = q.back[1:]
    }
}

func (q *IntQueue) PushBack(v int) {
    q.back = append(q.back, v)
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[0]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        q.front = q.front[1:]
    }
}

Це, в основному, дві стеки, де ми дозволяємо маніпулювати нижньою частиною один одного. Я також використовував конвенції про іменування STL, де традиційні операції натискання, поп, пік стека мають префікс передній / задній частині, чи стосується вони передньої чи задньої черги.

Проблема з вищевказаним кодом полягає в тому, що він не використовує пам'ять дуже ефективно. Насправді він росте нескінченно, поки не вистачає місця. Це справді погано. Виправлення цього полягає в тому, щоб, коли це можливо, просто використати нижню частину місця стеку. Ми повинні ввести зміщення, щоб відстежувати це, оскільки фрагмент у Go не може рости спереду, як тільки зменшився.

type IntQueue struct {
    front       []int
    frontOffset int
    back        []int
    backOffset  int
}

func (q *IntQueue) PushFront(v int) {
    if q.backOffset > 0 {
        i := q.backOffset - 1
        q.back[i] = v
        q.backOffset = i
    } else {
        q.front = append(q.front, v)
    }
}

func (q *IntQueue) Front() int {
    if len(q.front) > 0 {
        return q.front[len(q.front)-1]
    } else {
        return q.back[q.backOffset]
    }
}

func (q *IntQueue) PopFront() {
    if len(q.front) > 0 {
        q.front = q.front[:len(q.front)-1]
    } else {
        if len(q.back) > 0 {
            q.backOffset++
        } else {
            panic("Cannot pop front of empty queue.")
        }
    }
}

func (q *IntQueue) PushBack(v int) {
    if q.frontOffset > 0 {
        i := q.frontOffset - 1
        q.front[i] = v
        q.frontOffset = i
    } else {
        q.back = append(q.back, v)
    }
}

func (q *IntQueue) Back() int {
    if len(q.back) > 0 {
        return q.back[len(q.back)-1]
    } else {
        return q.front[q.frontOffset]
    }
}

func (q *IntQueue) PopBack() {
    if len(q.back) > 0 {
        q.back = q.back[:len(q.back)-1]
    } else {
        if len(q.front) > 0 {
            q.frontOffset++
        } else {
            panic("Cannot pop back of empty queue.")
        }
    }
}

Це дуже багато маленьких функцій, але з 6 функцій 3 з них - лише дзеркала іншої.


Тут ви використовуєте масиви. Я не бачу, де твої стеки.
мельпомена

@melpomene ОК, якщо ви придивитесь уважніше, ви помітите, що єдиними операціями, які я виконую, є додавання / видалення останнього елемента з масиву. Іншими словами, штовхаючи і вискакуючи. Для всіх намірів і цілей це стеки, але реалізовані за допомогою масивів.
Джон Лейдегрен

@melpomene Насправді, це лише половина праворуч, я припускаю, що удвічі закінчилися стеки. Я дозволяю змінювати стек нестандартним способом знизу вгору за певних умов.
Джон Лейдегрен

0

ось моє рішення в java за допомогою пов'язаного списку.

class queue<T>{
static class Node<T>{
    private T data;
    private Node<T> next;
    Node(T data){
        this.data = data;
        next = null;
    }
}
Node firstTop;
Node secondTop;

void push(T data){
    Node temp = new Node(data);
    temp.next = firstTop;
    firstTop = temp;
}

void pop(){
    if(firstTop == null){
        return;
    }
    Node temp = firstTop;
    while(temp != null){
        Node temp1 = new Node(temp.data);
        temp1.next = secondTop;
        secondTop = temp1;
        temp = temp.next;
    }
    secondTop = secondTop.next;
    firstTop = null;
    while(secondTop != null){
        Node temp3 = new Node(secondTop.data);
        temp3.next = firstTop;
        firstTop = temp3;
        secondTop = secondTop.next;
    }
}

}

Примітка. У цьому випадку поп-операція вимагає багато часу. Тому я не пропоную створювати чергу за допомогою двох стеків.


0

З O(1) dequeue(), що таке, як відповідь pythonquick :

// time: O(n), space: O(n)
enqueue(x):
    if stack.isEmpty():
        stack.push(x)
        return
    temp = stack.pop()
    enqueue(x)
    stack.push(temp)

// time: O(1)
x dequeue():
    return stack.pop()

З O(1) enqueue()(про це не згадується у цій публікації, тому ця відповідь), який також використовує зворотний трек для підключення та повернення нижнього елемента.

// O(1)
enqueue(x):
    stack.push(x)

// time: O(n), space: O(n)
x dequeue():
    temp = stack.pop()
    if stack.isEmpty():
        x = temp
    else:
        x = dequeue()
        stack.push(temp)
    return x

Очевидно, що це хороша вправа кодування, оскільки вона неефективна, але елегантна.


0

** Легке рішення JS **

  • Примітка. Я брав ідеї від інших коментарів

/*

enQueue(q,  x)
 1) Push x to stack1 (assuming size of stacks is unlimited).

deQueue(q)
 1) If both stacks are empty then error.
 2) If stack2 is empty
   While stack1 is not empty, push everything from stack1 to stack2.
 3) Pop the element from stack2 and return it.

*/
class myQueue {
    constructor() {
        this.stack1 = [];
        this.stack2 = [];
    }

    push(item) {
        this.stack1.push(item)
    }

    remove() {
        if (this.stack1.length == 0 && this.stack2.length == 0) {
            return "Stack are empty"
        }

        if (this.stack2.length == 0) {

            while (this.stack1.length != 0) {
                this.stack2.push(this.stack1.pop())
            }
        }
        return this.stack2.pop()
    }


    peek() {
        if (this.stack2.length == 0 && this.stack1.length == 0) {
            return 'Empty list'
        }

        if (this.stack2.length == 0) {
            while (this.stack1.length != 0) {
                this.stack2.push(this.stack1.pop())
            }
        }

        return this.stack2[0]
    }

    isEmpty() {
        return this.stack2.length === 0 && this.stack1.length === 0;
    }

}

const q = new myQueue();
q.push(1);
q.push(2);
q.push(3);
q.remove()

console.log(q)


-1
public class QueueUsingStacks<T>
{
    private LinkedListStack<T> stack1;
    private LinkedListStack<T> stack2;

    public QueueUsingStacks()
    {
        stack1=new LinkedListStack<T>();
        stack2 = new LinkedListStack<T>();

    }
    public void Copy(LinkedListStack<T> source,LinkedListStack<T> dest )
    {
        while(source.Head!=null)
        {
            dest.Push(source.Head.Data);
            source.Head = source.Head.Next;
        }
    }
    public void Enqueue(T entry)
    {

       stack1.Push(entry);
    }
    public T Dequeue()
    {
        T obj;
        if (stack2 != null)
        {
            Copy(stack1, stack2);
             obj = stack2.Pop();
            Copy(stack2, stack1);
        }
        else
        {
            throw new Exception("Stack is empty");
        }
        return obj;
    }

    public void Display()
    {
        stack1.Display();
    }


}

Для кожної операції enqueue додаємо до вершини стека1. Для кожного dequeue ми видаляємо вміст stack1 в stack2 і видаляємо елемент у верхній частині стека. Складність часу - O (n) для dequeue, оскільки ми повинні скопіювати stack1 в stack2. часова складність анкету така ж, як і звичайний стек


Цей код неефективний (непотрібне копіювання) і порушений: if (stack2 != null)завжди правдивий, оскільки stack2інстанціюється в конструкторі.
мельпомена

-2

Реалізація черги за допомогою двох об’єктів java.util.Stack:

public final class QueueUsingStacks<E> {

        private final Stack<E> iStack = new Stack<>();
        private final Stack<E> oStack = new Stack<>();

        public void enqueue(E e) {
            iStack.push(e);
        }

        public E dequeue() {
            if (oStack.isEmpty()) {
                if (iStack.isEmpty()) {
                    throw new NoSuchElementException("No elements present in Queue");
                }
                while (!iStack.isEmpty()) {
                    oStack.push(iStack.pop());
                }
            }
            return oStack.pop();
        }

        public boolean isEmpty() {
            if (oStack.isEmpty() && iStack.isEmpty()) {
                return true;
            }
            return false;
        }

        public int size() {
            return iStack.size() + oStack.size();
        }

}

3
Цей код функціонально ідентичний відповіді Дейва Л. Він не додає нічого нового, навіть пояснення.
мельпомена

Він додає методи isEmpty () та size () разом з базовим обробкою виключень. Я відредагую, щоб додати пояснення.
realPK

1
Ніхто не просив цих додаткових методів, і вони тривіальні (по одному рядку кожен): return inbox.isEmpty() && outbox.isEmpty()і return inbox.size() + outbox.size(), відповідно. Код Дейва Л. вже видає виняток, коли ви виходите з порожньої черги. Оригінальне питання стосувалося навіть не Java; мова йшла про структури даних / алгоритми загалом. Реалізація Java була лише додатковою ілюстрацією.
мельпомена

1
Це чудове джерело для людей, які хочуть зрозуміти, як побудувати чергу з двох стеків, діаграми, безумовно, допомогли мені більше, ніж читати відповідь Дейва.
Kemal Tezer Dilsiz

@melpomene: Справа не в тому, що методи банальні, а необхідні. Інтерфейс черги на Java розширює ці методи з інтерфейсу Collection, оскільки вони потрібні.
realPK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.