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


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
Это правда, что наихудшим временем для одной операции pop является O (n) (где n - текущий размер очереди). Однако наихудшее время для последовательности из n операций очереди также равно O (n), что дает нам амортизированное постоянное время. Я бы не реализовал очередь таким образом, но это не так уж плохо.
Дейв Л.

1
@Tyler Если ваш стек основан на массивах, как и большинство, вы всегда получите O (n) худший случай для одной операции.
Томас Але

2
@Tyler: проверьте sgi.com/tech/stl/Deque.html . Deque "поддерживает произвольный доступ к элементам". Следовательно, deque и stack основаны на массивах. Это потому, что это дает вам лучшее местоположение и, следовательно, быстрее на практике.
Томас Ахл

13
@Newtang a) очередь 1,2,3 => Входящие [3,2,1] / Исходящие [] . б) деку. Исходящие пустые, поэтому refill => Входящие [] / Исходящие [1,2,3] . Вывести из папки исходящих , вернуть 1 => Входящие [] / Исходящие [2,3] . в) очередь 4,5 => Входящие [5,4] / Исходящие [2,3] . г) отмена. Исходящие не пустые, поэтому выскочить из исходящих, вернуть 2 => Входящие [5,4] / Исходящие [3] . Это имеет больше смысла?
Дейв Л.

226

A - Как перевернуть стек

Чтобы понять, как построить очередь с использованием двух стеков, вы должны понимать, как полностью изменить стековую последовательность. Помните, как работает стек, это очень похоже на стек блюдо на вашей кухне. Последнее промытый блюдо будет на вершине стека чистой, который называется L AST я п Р рвые вывода ут (LIFO) в области вычислительной техники.

Давайте представим наш стек как бутылку, как показано ниже;

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

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

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

Теперь наш стек представлен в виде бутылки, заполненной значениями 3,2,1. И мы хотим перевернуть стек так, чтобы верхний элемент стека был 1, а нижний элемент стека был 3. Что мы можем сделать? Мы можем взять бутылку и держать ее вверх дном, чтобы все значения перевернулись по порядку?

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

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

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

Итак, мы знаем, как перевернуть стек.

B - Использование двух стеков в качестве очереди

В предыдущей части я объяснил, как мы можем изменить порядок элементов стека. Это было важно, потому что если мы помещаем элементы в стек, и выводим их, то результат будет точно в обратном порядке очереди. Думая на примере, давайте поместим массив целых чисел {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 ), который расположен слева;

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

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

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

Проверьте порядок элементов в стеке вывода (стек № 2). Очевидно, что мы можем вытолкнуть элементы из стека вывода, чтобы вывод был таким же, как если бы мы исключили его из очереди. Таким образом, если мы выполним две операции удаления очереди, сначала мы получим {1, 2}соответственно. Тогда элемент 3 будет единственным элементом выходного стека, а входной стек будет пустым. Если мы поставим в очередь элементы 4 и 5, то состояние очереди будет следующим;

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

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

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

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

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

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

C - Реализация очереди, построенной с двумя стеками

Вот реализация в Java. Я не собираюсь использовать существующую реализацию стека, поэтому пример здесь будет изобретать колесо;

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
Это действительно помогло предотвратить ошибки тайм-аута, которые я получал во время поп-музыки. Я помещал элементы обратно в исходный стек, но в этом не было необходимости. Престижность!
Pranit Bankar

2
Все комментарии должны быть смоделированы после этого.
Lolololol ol

4
Мне действительно не нужно решение для этого, просто просмотр ... Но когда я вижу такой ответ, я просто влюбляюсь ... Отличный ответ !!!
Maverick

80

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

Принцип остается неизменным при вставке нового элемента в очередь:

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

Класс Queue, использующий только один стек, будет выглядеть следующим образом:

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. Для нерекурсивного алгоритма вы всегда можете получить один «дополнительный» стек из стека вызовов в языках, поддерживающих рекурсию.
Антти Уима

1
@ antti.huima А не могли бы вы объяснить, как это может быть квадратичная вставка ?! Из того, что я понимаю, insert выполняет n pop и n push-операций, так что это совершенно линейный алгоритм O (n).
LP_

1
@LP_ требуется квадратичное время O (n ^ 2) для вставки n itemsв очередь с использованием вышеуказанной структуры данных. сумма в (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

Однако временные сложности были бы еще хуже. Хорошая реализация очереди делает все в постоянное время.

редактировать

Не уверен, почему мой ответ был опущен здесь. Если мы программируем, мы заботимся о сложности времени, и использование двух стандартных стеков для создания очереди неэффективно. Это очень актуальный и актуальный момент. Если кто-то еще будет нуждаться в этом, мне было бы интересно узнать почему.

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

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


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

Это правда. У вас средний случай и сложность амортизированного времени одинаковы. Но по умолчанию обычно используется наихудший случай для каждой операции, и это O (n), где n - текущий размер структуры.
Тайлер

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

1
«Наихудший случай» и «амортизированный» - это два разных типа сложности времени. Нет смысла говорить, что «наихудший случай может быть амортизирован» - если вы могли бы сделать наихудший случай = амортизированный, то это было бы значительным улучшением; Вы бы просто говорили о наихудшем случае без усреднения.
Тайлер

Я не уверен, что вы подразумеваете под O (1) наихудшим случаем, когда он «строго лучше», чем комбинация O (1) среднего случая и O (n) наихудшего случая. Постоянные коэффициенты масштабирования имеют значение. Структура данных, которая, если она содержит N элементов, может потребоваться повторно упаковать после N операций в течение N микросекунд и в противном случае занимает одну микросекунду на операцию, может быть гораздо более полезной, чем та, которая занимает миллисекунду для каждой операции, даже если размер данных увеличится до миллионов элементов (что подразумевает, что некоторые отдельные операции будут занимать несколько секунд).
Суперкат

8

Пусть реализуемая очередь будет q, а стеки, используемые для реализации q, будут stack1 и stack2.

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

Способ 1 (сделав операцию enQueue дорогостоящей)

Этот метод гарантирует, что вновь введенный элемент всегда находится на вершине стека 1, так что операция deQueue просто появляется из stack1. Чтобы поместить элемент в верхнюю часть stack1, используется 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 дорогостоящей)

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

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 с шагами.
Зеленая капуста,


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: euqueued элементы всегда помещаются в stack1

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

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


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, в которых традиционные операции стека push, pop, peek имеют префикс front / back независимо от того, ссылаются ли они на начало или конец очереди.

Проблема с приведенным выше кодом заключается в том, что он не очень эффективно использует память. На самом деле, он растет бесконечно, пока вы не исчерпаете пространство. Это действительно плохо. Решением этой проблемы является простое повторное использование нижней части пространства стека, когда это возможно. Мы должны ввести смещение для отслеживания этого, так как срез в 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 функций три из них являются просто зеркалами другой.


Вы используете массивы здесь. Я не вижу, где ваши стеки.
Мельпомена

@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;
    }
}

}

Примечание. В этом случае операция pop занимает очень много времени. Поэтому я не буду предлагать создавать очередь, используя два стека.


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();
    }


}

Для каждой операции добавления в очередь мы добавляем в начало стека 1. Для каждой очереди мы опускаем содержимое стека 1 в стек 2 и удаляем элемент в верхней части стека. Сложность времени O (n) для очереди, так как мы должны скопировать stack1 в stack2. временная сложность enqueue такая же, как и у обычного стека


Этот код неэффективен (ненужное копирование) и не работает: if (stack2 != null)всегда имеет значение true, поскольку 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
Это отличный источник информации для людей, которые хотят понять, как построить очередь из двух стеков. Диаграммы, безусловно, помогли мне больше, чем чтение ответа Дейва.
Кемаль Тезер Дилсиз

@melpomene: Речь идет не о тривиальных методах, а о необходимости. Интерфейс очереди в Java расширяет эти методы из интерфейса Collection, потому что они необходимы.
realPK
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.