Come implementare una coda usando due stack?


394

Supponiamo di avere due stack e nessun'altra variabile temporanea.

È possibile "costruire" una struttura di dati di coda usando solo i due stack?

Risposte:


701

Mantieni 2 pile, chiamiamole inboxe outbox.

Accodare :

  • Inserire il nuovo elemento su inbox

Dequeue :

  • Se outboxè vuoto, riempilo facendo scoppiare ogni elemento inboxe spingendolooutbox

  • Pop e restituisce l'elemento superiore da outbox

Usando questo metodo, ogni elemento sarà in ogni pila esattamente una volta - il che significa che ogni elemento verrà spinto due volte e fatto scoppiare due volte, dando operazioni di tempo costante ammortizzate.

Ecco un'implementazione in 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
La complessità temporale peggiore è ancora O (n). Continuo a dirlo perché spero che nessuno degli studenti (sembra una domanda a casa / educativa) pensi che sia un modo accettabile per implementare una coda.
Tyler,

26
È vero che il momento peggiore per una singola operazione pop è O (n) (dove n è la dimensione corrente della coda). Tuttavia, il momento peggiore per una sequenza di n operazioni in coda è anche O (n), dandoci il tempo costante ammortizzato. Non implementerei una coda in questo modo, ma non è poi così male.
Dave L.,

1
@Tyler Se il tuo stack è basato su array, come molti altri, otterrai sempre il caso peggiore O (n) per una singola operazione.
Thomas Ahle,

2
@Tyler: controlla sgi.com/tech/stl/Deque.html . Deque "supporta l'accesso casuale agli elementi". Quindi sia deque che stack sono basati su array. Questo perché ti dà una migliore località di riferimento e quindi è più veloce nella pratica.
Thomas Ahle,

13
@Newtang a) coda 1,2,3 => Posta in arrivo [3,2,1] / Posta in uscita [] . b) dequeue. outbox è vuoto, quindi refill => Inbox [] / Outbox [1,2,3] . Pop dalla posta in uscita, restituisce 1 => Posta in arrivo [] / Posta in uscita [2,3] . c) coda 4,5 => Posta in arrivo [5,4] / Posta in uscita [2,3] . d) dequeue. outbox non è vuoto, quindi pop da outbox, restituisce 2 => Inbox [5,4] / Outbox [3] . Ha più senso?
Dave L.,

226

A - Come invertire una pila

Per capire come costruire una coda usando due pile, dovresti capire come invertire una pila in modo cristallino. Ricorda come funziona la pila, è molto simile alla pila di piatti della tua cucina. L'ultimo piatto lavato sarà in cima alla pila pulita, che in informatica è chiamata L ast I n F irst O ut (LIFO).

Immaginiamo il nostro stack come una bottiglia come di seguito;

inserisci qui la descrizione dell'immagine

Se spingiamo interi 1,2,3 rispettivamente, 3 sarà in cima allo stack. Poiché 1 verrà premuto per primo, quindi 2 verrà inserito in cima a 1. Infine, 3 verrà inserito in cima allo stack e l'ultimo stato del nostro stack rappresentato come una bottiglia sarà il seguente;

inserisci qui la descrizione dell'immagine

Ora abbiamo il nostro stack rappresentato come una bottiglia è popolata con valori 3,2,1. E vogliamo invertire la pila in modo che l'elemento superiore della pila sia 1 e l'elemento inferiore della pila sia 3. Cosa possiamo fare? Possiamo prendere la bottiglia e tenerla sottosopra in modo che tutti i valori siano invertiti in ordine?

inserisci qui la descrizione dell'immagine

Sì, possiamo farlo, ma quella è una bottiglia. Per fare lo stesso processo, dobbiamo disporre di un secondo stack che memorizzerà i primi elementi dello stack in ordine inverso. Mettiamo il nostro stack popolato a sinistra e il nostro nuovo stack vuoto a destra. Per invertire l'ordine degli elementi, faremo scoppiare ogni elemento dalla pila di sinistra e li sposteremo nella pila di destra. Puoi vedere cosa succede mentre lo facciamo nell'immagine qui sotto;

inserisci qui la descrizione dell'immagine

Quindi sappiamo come invertire uno stack.

B - Utilizzo di due stack come coda

Nella parte precedente, ho spiegato come possiamo invertire l'ordine degli elementi dello stack. Questo era importante, perché se spingiamo e pop gli elementi nello stack, l'output sarà esattamente nell'ordine inverso di una coda. Pensando a un esempio, spingiamo l'array di numeri interi {1, 2, 3, 4, 5}in uno stack. Se pop gli elementi e li stampiamo fino a quando la pila non è vuota, avremo l'array nell'ordine inverso dell'ordine di push, che sarà {5, 4, 3, 2, 1}Ricorda che per lo stesso input, se dequeue la coda fino a quando la coda è vuota, l'output sarà {1, 2, 3, 4, 5}. Quindi è ovvio che per lo stesso ordine di input degli elementi, l'output della coda è esattamente il contrario dell'output di uno stack. Poiché sappiamo come invertire uno stack utilizzando uno stack aggiuntivo, possiamo costruire una coda utilizzando due stack.

Il nostro modello di coda sarà composto da due pile. Uno stack verrà utilizzato per l' enqueueoperazione (stack n. 1 a sinistra, verrà chiamato come stack di input), verrà utilizzato un altro stack per l' dequeueoperazione (stack n. 2 a destra, verrà chiamato come stack di output). Guarda l'immagine qui sotto;

inserisci qui la descrizione dell'immagine

Il nostro pseudo-codice è il seguente;


Enqueue Operation

Push every input element to the Input Stack

Dequeue Operation

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

Accodiamo gli interi {1, 2, 3}rispettivamente. I numeri interi verranno inseriti nello Stack di input ( Stack # 1 ) che si trova a sinistra;

inserisci qui la descrizione dell'immagine

Quindi cosa succederà se eseguiamo un'operazione di dequeue? Ogni volta che viene eseguita un'operazione di dequeue, la coda verifica se lo stack di output è vuoto o meno (vedere lo pseudo-codice sopra) Se lo stack di output è vuoto, lo stack di input verrà estratto sull'output in modo che gli elementi dello stack di input verrà invertito. Prima di restituire un valore, lo stato della coda sarà il seguente;

inserisci qui la descrizione dell'immagine

Controlla l'ordine degli elementi nello Stack di output (Stack # 2). È ovvio che possiamo estrarre gli elementi dallo Stack di output in modo che l'output sia lo stesso di se fossimo in coda di coda. Pertanto, se eseguiamo due operazioni di dequeue, prima otterremo {1, 2}rispettivamente. Quindi l'elemento 3 sarà l'unico elemento dello Stack di output e lo Stack di input sarà vuoto. Se accodiamo gli elementi 4 e 5, lo stato della coda sarà il seguente;

inserisci qui la descrizione dell'immagine

Ora lo Stack di output non è vuoto e se eseguiamo un'operazione di dequeue, solo 3 verranno estratti dallo Stack di output. Quindi lo stato verrà visualizzato come di seguito;

inserisci qui la descrizione dell'immagine

Ancora una volta, se eseguiamo altre due operazioni di dequeue, sulla prima operazione di dequeue, la coda controllerà se lo Stack di output è vuoto, il che è vero. Quindi estrarre gli elementi dello stack di input e spingerli nello stack di output fino a quando lo stack di input è vuoto, quindi lo stato della coda sarà il seguente;

inserisci qui la descrizione dell'immagine

Facile da vedere, l'output delle due operazioni di dequeue sarà {4, 5}

C - Implementazione della coda costruita con due stack

Ecco un'implementazione in Java. Non userò l'implementazione esistente di Stack, quindi l'esempio qui reinventerà la ruota;

C - 1) Classe MyStack: un'implementazione semplice dello stack

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) Classe MyQueue: implementazione della coda usando due stack

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) Codice demo

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) Uscita campione

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

18
Lo farei +1 tutto il giorno se potessi. Non riuscivo a capire come fosse stato ammortizzato a tempo costante. Le tue illustrazioni hanno chiarito le cose, in particolare la parte di lasciare gli elementi rimanenti nello stack di output e ricaricarli solo quando si svuota.
Shane McQuillan,

1
Questo ha davvero aiutato a prevenire gli errori di timeout che stavo riscontrando durante il pop. Stavo riposizionando gli elementi nella pila originale ma non c'era bisogno di farlo. Complimenti!
Pranit Bankar,

2
Tutti i commenti dovrebbero essere modellati dopo questo.
lolololol ol

4
Non avevo davvero bisogno di una soluzione per questo, solo navigando ... Ma quando vedo una risposta come questa mi innamoro semplicemente .. Ottima risposta !!!
Maverick,

80

Puoi persino simulare una coda usando solo uno stack. Il secondo stack (temporaneo) può essere simulato dallo stack di chiamate ricorsive al metodo insert.

Il principio rimane lo stesso quando si inserisce un nuovo elemento nella coda:

  • È necessario trasferire elementi da una pila a un'altra pila temporanea, per invertire il loro ordine.
  • Quindi spingere il nuovo elemento da inserire, nello stack temporaneo
  • Quindi trasferire gli elementi nella pila originale
  • Il nuovo elemento sarà nella parte inferiore della pila e l'elemento più vecchio è in cima (il primo ad essere spuntato)

Una classe di coda che utilizza un solo stack sarebbe la seguente:

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
Forse il codice sembra elegante ma è molto inefficiente (inserire O (n ** 2)) e ha ancora due stack, uno nell'heap e uno nello stack delle chiamate, come sottolinea @pythonquick. Per un algoritmo non ricorsivo, puoi sempre prendere uno stack "extra" dallo stack di chiamate in lingue che supportano la ricorsione.
Antti Huima,

1
@ antti.huima E spiegheresti come potrebbe essere un inserto quadratico ?! Da quello che ho capito, insert fa n operazioni pop e n push, quindi è un algoritmo O (n) perfettamente lineare.
LP_17

1
@LP_ ci vuole tempo quadratico O (n ^ 2) per inserire n itemsnella coda usando la struttura dati sopra. la somma (1 + 2 + 4 + 8 + .... + 2(n-1))risulta ~O(n^2). Spero che tu capisca il punto.
Ankit Kumar,

1
@ antti.huima Stavi parlando della complessità della funzione insert (hai detto "O (n 2) insert" - probabilmente intendevi "O (n 2) fill"). Per convenzione , "l'inserzione della complessità" è il tempo impiegato da un inserimento, che è qui lineare nel numero di elementi già presenti. Se parliamo nel tempo necessario per inserire n elementi, diremmo che una tabella hash ha un inserimento lineare. Non è così.
LP_15

2
Stai essenzialmente usando la pila, come pila. Ciò significa che se nello stack è presente un numero elevato di elementi, è possibile finire con un overflow dello stack: è quasi come se la soluzione fosse progettata per questo sito!
UKMonkey,

11

Le complessità temporali sarebbero comunque peggiori. Una buona implementazione della coda fa tutto in tempo costante.

modificare

Non sono sicuro del motivo per cui la mia risposta è stata sottoposta a downgrade. Se programmiamo, ci preoccupiamo della complessità del tempo e l'uso di due stack standard per creare una coda è inefficiente. È un punto molto valido e pertinente. Se qualcun altro sente la necessità di sottovalutare ulteriormente questo, sarei interessato a sapere perché.

Un po 'più di dettaglio : perché usare due pile è peggio di una semplice coda: se usi due pile e qualcuno chiama dequeue mentre la posta in uscita è vuota, hai bisogno di tempo lineare per arrivare in fondo alla posta in arrivo (come puoi vedere nel codice di Dave).

È possibile implementare una coda come un elenco collegato singolarmente (ogni elemento punta all'elemento inserito successivamente), mantenendo un puntatore aggiuntivo all'ultimo elemento inserito per i push (o rendendolo un elenco ciclico). L'implementazione di code e dequeue su questa struttura di dati è molto semplice da eseguire in tempo costante. È il tempo costante nel caso peggiore, non ammortizzato. E, poiché i commenti sembrano chiedere questo chiarimento, il tempo costante nel caso peggiore è strettamente migliore del tempo costante ammortizzato.


Non nel caso medio. La risposta di Brian descrive una coda che avrebbe ammortizzato le continue operazioni di accodamento e dequeue.
Daniel Spiewak,

È vero. La complessità media del caso e il tempo ammortizzato sono gli stessi. Ma il valore predefinito è in genere il caso peggiore per operazione, e questo è O (n) dove n è la dimensione corrente della struttura.
Tyler,

1
Il caso peggiore può anche essere ammortizzato. Ad esempio, le matrici dinamiche mutabili (vettori) sono generalmente considerate con tempo di inserimento costante, anche se ogni tanto è richiesta una costosa operazione di ridimensionamento e copia.
Daniel Spiewak,

1
"Peggiore" e "ammortizzato" sono due diversi tipi di complessità temporale. Non ha senso affermare che "il caso peggiore può essere ammortizzato" - se si potesse fare il caso peggiore = l'ammortizzato, si tratterebbe di un miglioramento significativo; parleresti del caso peggiore, senza media.
Tyler,

Non sono sicuro che cosa intendi per O (1) nel caso peggiore essendo "strettamente migliore" di una combinazione di O (1) caso medio e O (n) nel caso peggiore. I fattori di ridimensionamento costante contano. Una struttura di dati che, se contiene N articoli, potrebbe dover essere reimballata dopo N operazioni al momento di N microsecondi, e altrimenti impiega un microsecondo per operazione, può essere molto più utile di una che richiede un millisecondo per ogni operazione, anche se la dimensione dei dati si espanderà a milioni di elementi (il che implica che alcune singole operazioni richiederebbero più secondi).
supercat

8

Lascia che la coda da implementare sia q e gli stack utilizzati per implementare q siano stack1 e stack2.

q può essere implementato in due modi:

Metodo 1 (Rendendo l'operazione enQueue costosa)

Questo metodo assicura che l'elemento appena inserito sia sempre all'inizio dello stack 1, in modo che l'operazione deQueue venga appena visualizzata dallo stack1. Per posizionare l'elemento in cima a stack1, viene utilizzato 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.

Metodo 2 (rendendo costosa l'operazione deQueue)

In questo metodo, nell'operazione in coda, il nuovo elemento viene inserito nella parte superiore dello stack1. Nel funzionamento in coda, se stack2 è vuoto, tutti gli elementi vengono spostati in stack2 e infine viene restituita la parte superiore di 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.

Il metodo 2 è decisamente migliore del metodo 1. Il metodo 1 sposta due volte tutti gli elementi nell'operazione enQueue, mentre il metodo 2 (nell'operazione deQueue) sposta gli elementi una volta e sposta gli elementi solo se stack2 vuoto.


Nessuna delle soluzioni che ho capito, tranne per il tuo metodo 2. Adoro il modo in cui lo spieghi con il metodo accodamento e dequeue con i passaggi.
theGreenCabbage


3

Una soluzione in 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

Due stack nella coda sono definiti come stack1 e stack2 .

Enqueue: gli elementi euqueued vengono sempre inseriti nello stack1

Dequeue: la parte superiore di stack2 può essere estratta poiché è il primo elemento inserito nella coda quando stack2 non è vuoto. Quando stack2 è vuoto, estraiamo tutti gli elementi dallo stack1 e li spingiamo nello stack2 uno per uno. Il primo elemento in una coda viene inserito nella parte inferiore dello stack1 . Può essere estratto direttamente dopo aver fatto scoppiare e spingere le operazioni poiché è in cima allo stack2 .

Quanto segue è lo stesso codice di esempio 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;
}

Questa soluzione è presa in prestito dal mio blog . Analisi più dettagliate con simulazioni operative dettagliate sono disponibili nella pagina Web del mio blog.


2

Dovrai estrarre tutto dal primo stack per ottenere l'elemento in basso. Quindi rimettili tutti sul secondo stack per ogni operazione di "dequeue".


3
Si hai ragione. Mi chiedo come hai ottenuto così tanti voti negativi. Ho votato a favore della tua risposta
Binita Bharati,

È inquietante vedere che questa è stata la sua ultima risposta ed è stato un decennio da allora.
Shanu Gupta,

2

per lo sviluppatore c # ecco il programma completo:

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

Implementare le seguenti operazioni di una coda usando stack.

push (x) - Spinge l'elemento x sul retro della coda.

pop () - Rimuove l'elemento davanti alla coda.

peek () - Ottieni l'elemento frontale.

empty () - Restituisce se la coda è vuota.

inserisci qui la descrizione dell'immagine

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

Un'implementazione di una coda che utilizza due stack in 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

Mentre otterrai molti post relativi all'implementazione di una coda con due stack: 1. O rendendo il processo enQueue molto più costoso 2. O rendendo il processo deQueue molto più costoso

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

Un modo importante che ho scoperto dal precedente post era la costruzione di una coda con solo la struttura dei dati dello stack e lo stack delle chiamate di ricorsione.

Mentre si può sostenere che letteralmente si utilizzano ancora due stack, ma idealmente si utilizza solo una struttura di dati dello stack.

Di seguito è la spiegazione del problema:

  1. Dichiarare uno stack singolo per enQueuing e deQueing dei dati e inviare i dati nello stack.

  2. mentre deQueueing ha una condizione di base in cui l'elemento dello stack viene popolato quando la dimensione dello stack è 1. Questo assicurerà che non ci sia overflow dello stack durante la ricorsione di deQueue.

  3. Mentre deQueueing prima pop i dati dalla parte superiore dello stack. Idealmente questo elemento sarà l'elemento presente nella parte superiore della pila. Ora, una volta fatto questo, chiama ricorsivamente la funzione deQueue e poi rimetti nello stack l'elemento spuntato sopra.

Il codice sarà simile al seguente:

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;

In questo modo è possibile creare una coda utilizzando una struttura dati a stack singolo e lo stack di chiamate di ricorsione.


1

Di seguito è la soluzione in linguaggio javascript che utilizza la sintassi 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 };

Di seguito è riportato l'uso:

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"

Questo è infastidito. Se si accodano più elementi dopo la dequeue, li inserirai stack1. Quando vai di dequeuenuovo, li sposterai in oggetti stack2, mettendoli davanti a ciò che era già lì.
Alexander - Ripristina Monica il

0

Risponderò a questa domanda in Go perché Go non ha molte raccolte nella sua libreria standard.

Dato che uno stack è davvero facile da implementare, ho pensato di provare a usare due stack per realizzare una coda a doppio attacco. Per capire meglio come sono arrivato alla mia risposta, ho diviso l'implementazione in due parti, la prima parte si spera sia più facile da capire ma è incompleta.

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:]
    }
}

Fondamentalmente sono due pile in cui permettiamo che il fondo delle pile sia manipolato l'uno dall'altro. Ho anche usato le convenzioni di denominazione STL, in cui le tradizionali operazioni push, pop, peek di uno stack hanno un prefisso fronte / retro sia che si riferiscano alla parte anteriore o posteriore della coda.

Il problema con il codice sopra è che non utilizza la memoria in modo molto efficiente. In realtà, cresce all'infinito fino a quando non si esaurisce lo spazio. È davvero brutto. La soluzione per questo è semplicemente riutilizzare il fondo dello spazio dello stack quando possibile. Dobbiamo introdurre un offset per tracciare questo dato che una porzione in Go non può crescere nella parte anteriore una volta ridotta.

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.")
        }
    }
}

Sono molte le piccole funzioni ma delle 6 funzioni 3 sono solo specchi dell'altra.


Stai usando array qui. Non vedo dove sono le tue pile.
melpomene,

@melpomene OK, se dai un'occhiata più da vicino noterai che l'unica operazione che sto eseguendo è l'aggiunta / rimozione dell'ultimo elemento dell'array. In altre parole, spingendo e scoppiando. A tutti gli effetti questi sono stack ma implementati usando array.
John Leidegren,

@melpomene In realtà, è solo la metà, suppongo che le pile siano doppie. Sto permettendo che lo stack venga modificato in modo non standard dal basso verso l'alto in determinate condizioni.
John Leidegren,

0

ecco la mia soluzione in java usando linklist.

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

}

Nota: in questo caso, l'operazione pop richiede molto tempo. Quindi non suggerirò di creare una coda usando due stack.


0

Con O(1) dequeue(), che è uguale alla risposta di 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()

Con O(1) enqueue()(questo non è menzionato in questo post, quindi questa risposta), che utilizza anche il backtracking per eseguire il bubble up e restituire l'elemento più in basso.

// 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

Ovviamente, è un buon esercizio di codifica in quanto inefficiente ma elegante comunque.


0

** Soluzione JS facile **

  • Nota: ho preso idee dal commento di altre persone

/*

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


}

Per ogni operazione di accodamento, aggiungiamo all'inizio dello stack1. Per ogni dequeue, svuotiamo il contenuto dello stack1 nello stack2 e rimuoviamo l'elemento in cima allo stack. La complessità temporale è O (n) per il dequeue, poiché dobbiamo copiare lo stack1 in stack2. la complessità temporale di accodamento è la stessa di uno stack normale


Questo codice è inefficiente (copia non necessaria) e non funzionante: if (stack2 != null)è sempre vero perché stack2viene istanziato nel costruttore.
melpomene,

-2

Implementazione della coda usando due oggetti 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
Questo codice è funzionalmente identico alla risposta di Dave L. Non aggiunge nulla di nuovo, nemmeno una spiegazione.
melpomene,

Aggiunge i metodi isEmpty () e size () insieme alla gestione delle eccezioni di base. Modificherò per aggiungere una spiegazione.
realPK,

1
Nessuno ha chiesto quei metodi extra, e sono banali (una riga ciascuno): return inbox.isEmpty() && outbox.isEmpty()e return inbox.size() + outbox.size(), rispettivamente. Il codice di Dave L. genera già un'eccezione quando si esegue il dequeue da una coda vuota. La domanda originale non riguardava nemmeno Java; si trattava di strutture / algoritmi di dati in generale. L'implementazione Java era solo un'ulteriore illustrazione.
melpomene,

1
Questa è un'ottima fonte per le persone che cercano di capire come costruire la coda da due pile, i diagrammi sicuramente mi hanno aiutato più che leggere la risposta di Dave.
Kemal Tezer Dilsiz,

@melpomene: non si tratta di metodi banali ma di necessità. L'interfaccia della coda in Java estende questi metodi dall'interfaccia Collection perché sono necessari.
realPK
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.