La risposta che segue è "barare", in quanto mentre non utilizza alcuno spazio tra le operazioni, le operazioni stesse possono usare più dello spazio . Vedi altrove in questa discussione per una risposta che non presenta questo problema.O(1)
Anche se non ho una risposta alla tua domanda esatta, ho trovato un algoritmo che funziona nel tempo invece di . Credo che questo sia stretto, anche se non ho una prova. Semmai, l'algoritmo mostra che cercare di dimostrare un limite inferiore di è inutile, quindi potrebbe aiutare a rispondere alla tua domanda.O(n)O(n)O(n−−√)O(n)O(n)
Vi presento due algoritmi, il primo è un semplice algoritmo con un tempo di esecuzione per Pop e il secondo con un tempo di esecuzione per Pop. Descrivo il primo principalmente per la sua semplicità, in modo che il secondo sia più facile da capire.O ( √O(n)O(n−−√)
Per essere più dettagliati: il primo non usa spazio aggiuntivo, ha un caso peggiore (e ammortizzato) Push e un caso peggiore (e ammortizzato) Pop, ma il comportamento del caso peggiore non è sempre attivato. Dal momento che non utilizza spazio aggiuntivo oltre le due code, è leggermente "migliore" della soluzione offerta da Ross Snider.O ( n )O(1)O(n)
Il secondo utilizza un singolo campo intero (quindi spazio extra), ha un caso peggiore (e ammortizzato) Push e un Pop ammortizzato. Il suo tempo di esecuzione è quindi significativamente migliore di quello dell'approccio "semplice", eppure utilizza un po 'di spazio in più.O ( 1 ) O ( √O(1)O(1)O(n−−√)
Il primo algoritmo
Abbiamo due code: la coda e la coda . sarà la nostra "coda di invio", mentre la sarà la coda già in "ordine di stack".s e c o n d f i r s t s e c o n dfirstsecondfirstsecond
- La spinta viene eseguita semplicemente accodando il parametro sul .first
- Il popping viene eseguito come segue. Se il è vuoto, dobbiamo semplicemente rimuovere il e restituire il risultato. Altrimenti, invertiamo , aggiungiamo tutto il al e scambiamo il e il . Abbiamo poi dequeue e restituire il risultato della dequeue.s e c o n d f i r s t s e c o n d f i r s t f i r s t s e c o n dfirstsecondfirstsecondfirstfirstsecondsecond
Codice C # per il primo algoritmo
Questo potrebbe essere abbastanza leggibile, anche se non hai mai visto C # prima. Se non sai cosa sono i generici, sostituisci semplicemente tutte le istanze di "T" con "stringa" nella tua mente, per una pila di stringhe.
public class Stack<T> {
private Queue<T> first = new Queue<T>();
private Queue<T> second = new Queue<T>();
public void Push(T value) {
first.Enqueue(value);
}
public T Pop() {
if (first.Count == 0) {
if (second.Count > 0)
return second.Dequeue();
else
throw new InvalidOperationException("Empty stack.");
} else {
int nrOfItemsInFirst = first.Count;
T[] reverser = new T[nrOfItemsInFirst];
// Reverse first
for (int i = 0; i < nrOfItemsInFirst; i++)
reverser[i] = first.Dequeue();
for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
first.Enqueue(reverser[i]);
// Append second to first
while (second.Count > 0)
first.Enqueue(second.Dequeue());
// Swap first and second
Queue<T> temp = first; first = second; second = temp;
return second.Dequeue();
}
}
}
Analisi
Ovviamente Push funziona in tempo. Pop può toccare tutto dentro per e per un numero costante di volte, quindi abbiamo nel peggiore dei casi. L'algoritmo mostra questo comportamento (per esempio) se uno spinge elementi nello stack e quindi esegue ripetutamente una singola Push e una singola operazione Pop in successione.f i r s t s e c o n d O ( n ) nO(1)firstsecondO(n)n
Il secondo algoritmo
Abbiamo due code: la coda e la coda . sarà la nostra "coda di invio", mentre la sarà la coda già in "ordine di stack".s e c o n d f i r s t s e c o n dfirstsecondfirstsecond
Questa è una versione adattata del primo algoritmo, in cui non "rimescoliamo" immediatamente il contenuto del in . Invece, se contiene un numero sufficientemente piccolo di elementi rispetto al (vale a dire la radice quadrata del numero di elementi in ), riorganizziamo solo il in ordine di stack e non lo uniamo al .firstsecondfirstsecondsecondfirstsecond
- La spinta viene comunque eseguita semplicemente accodando il parametro sul .first
- Il popping viene eseguito come segue. Se il è vuoto, dobbiamo semplicemente rimuovere il e restituire il risultato. Altrimenti, riorganizziamo i contenuti di modo che siano nell'ordine dello stack. Se semplicemente dequeue e restituiamo il risultato. Altrimenti, aggiungiamo il al , scambiamo il e il , dequeue il e restituiamo il risultato.firstsecondfirst|first|<|second|−−−−−−−√firstsecondfirstfirstsecondsecond
Codice C # per il primo algoritmo
Questo potrebbe essere abbastanza leggibile, anche se non hai mai visto C # prima. Se non sai cosa sono i generici, sostituisci semplicemente tutte le istanze di "T" con "stringa" nella tua mente, per una pila di stringhe.
public class Stack<T> {
private Queue<T> first = new Queue<T>();
private Queue<T> second = new Queue<T>();
int unsortedPart = 0;
public void Push(T value) {
unsortedPart++;
first.Enqueue(value);
}
public T Pop() {
if (first.Count == 0) {
if (second.Count > 0)
return second.Dequeue();
else
throw new InvalidOperationException("Empty stack.");
} else {
int nrOfItemsInFirst = first.Count;
T[] reverser = new T[nrOfItemsInFirst];
for (int i = nrOfItemsInFirst - unsortedPart - 1; i >= 0; i--)
reverser[i] = first.Dequeue();
for (int i = nrOfItemsInFirst - unsortedPart; i < nrOfItemsInFirst; i++)
reverser[i] = first.Dequeue();
for (int i = nrOfItemsInFirst - 1; i >= 0; i--)
first.Enqueue(reverser[i]);
unsortedPart = 0;
if (first.Count * first.Count < second.Count)
return first.Dequeue();
else {
while (second.Count > 0)
first.Enqueue(second.Dequeue());
Queue<T> temp = first; first = second; second = temp;
return second.Dequeue();
}
}
}
}
Analisi
Ovviamente Push funziona in tempo.O(1)
Pop funziona in tempo ammortizzato . Esistono due casi: if , quindi spostiamo per nell'ordine dello stack in . Se , quindi abbiamo avuto almeno chiamate per Push. Quindi, possiamo solo toccare questo caso ogni chiamate a Push and Pop. Il tempo di esecuzione effettivo per questo caso è , quindi il tempo ammortizzato è .O(n−−√)|first|<|second|−−−−−−−√firstO(|first|)=O(n−−√)|first|≥|second|−−−−−−−√n−−√ O(n)O( nn−−√O(n)O(nn√)=O(n−−√)
Nota finale
È possibile eliminare la variabile aggiuntiva al costo di eseguire un'operazione Pop an , facendo in modo che Pop riorganizzi ad ogni chiamata invece che Push faccia tutto il lavoro.firstO(n−−√)first