Как реализовать очередь, используя два стека? - PullRequest
373 голосов
/ 16 сентября 2008

Предположим, у нас есть два стека и нет другой временной переменной.

Можно ли "построить" структуру данных очереди, используя только два стека?

Ответы [ 18 ]

674 голосов
/ 16 сентября 2008

Держите 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();
    }

}
197 голосов
/ 23 августа 2016

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

Чтобы понять, как построить очередь с использованием двух стеков, вы должны понимать, как полностью изменить стековую последовательность. Помните, как работает стек, это очень похоже на стек блюдо на вашей кухне. Последнее вымытое блюдо будет на вершине чистой стопки, которая называется L ast I n F irst O ut (LIFO) в области компьютерных наук.

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

enter image description here

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

enter image description here

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

enter image description here

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

enter image description here

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

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

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

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

enter image description here

Наш псевдокод, как показано ниже;


Операция постановки в очередь

Push every input element to the Input Stack

Операция удаления из очереди

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} соответственно. Целые числа будут помещены во входной стек ( Stack # 1 ), расположенный слева;

enter image description here

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

enter image description here

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

enter image description here

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

enter image description here

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

enter image description here

Легко увидеть, результат двух операций удаления из очереди будет {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
80 голосов
/ 17 сентября 2008

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

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

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

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

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();
    }
}
11 голосов
/ 16 сентября 2008

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

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

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

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

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

7 голосов
/ 31 марта 2014

Пусть реализуемая очередь будет 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 пуст.

3 голосов
/ 31 мая 2017

Решение в 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 голосов
/ 15 ноября 2016

для разработчика на 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 голосов
/ 16 сентября 2008

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

2 голосов
/ 29 декабря 2012

Два стека в очереди определены как stack1 и stack2 .

Ставить: Элементы euqueued всегда помещаются в stack1

Dequeue: Вершина stack2 может быть выдвинута, поскольку это первый элемент, вставленный в очередь, когда stack2 не пуст. Когда stack2 пуст, мы извлекаем все элементы из stack1 и помещаем их в stack2 один за другим. Первый элемент в очереди помещается в нижнюю часть stack1 . Его можно вытолкнуть сразу после операций выталкивания и нажатия, поскольку он находится на вершине stack2 .

Ниже приведен пример кода 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;
}

Это решение заимствовано из моего блога . Более подробный анализ с пошаговым моделированием работы доступен на моей странице в блоге.

1 голос
/ 17 октября 2017

Реализация очереди с использованием двух стеков в 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()!)
            }
        }
    }
}
...