La respuesta a continuación es 'trampa', ya que si bien no usa ningún espacio entre operaciones, las operaciones en sí pueden usar más de espacio. Vea en otra parte de este hilo una respuesta que no tiene este problema.O(1)
Si bien no tengo una respuesta a su pregunta exacta, sí encontré un algoritmo que funciona en tiempo en lugar deO(n). Creo que esto es apretado, aunque no tengo una prueba. En todo caso, el algoritmo muestra que intentar probar un límite inferior deO(n)es inútil, por lo que podría ayudar a responder su pregunta.O(n−−√)O(n)O(n)
Presento dos algoritmos, el primero es un algoritmo simple con un tiempo de ejecución para Pop y el segundo con un O ( √O ( n )tiempo de ejecución para Pop. Describo el primero principalmente por su simplicidad para que el segundo sea más fácil de entender.O ( n--√)
Para dar más detalles: el primero no utiliza espacio adicional, tiene un peor caso (y amortizado) Push y un O ( n ) peor caso (y amortizado) Pop, pero el comportamiento del peor de los casos no siempre se activa. Dado que no utiliza ningún espacio adicional más allá de las dos colas, es ligeramente "mejor" que la solución ofrecida por Ross Snider.O ( 1 )O ( n )
El segundo usa un solo campo entero (entonces espacio extra), tiene un O ( 1 ) peor caso (y amortizado) Push y un O ( √O ( 1 )O ( 1 )Pop amortizado. Su tiempo de ejecución es, por lo tanto, significativamente mejor que el del enfoque 'simple', pero utiliza algo de espacio extra.O ( n--√)
El primer algoritmo
Tenemos dos colas: cola y cola s e c o n d . f i r s t será nuestra 'cola de empuje', mientras que s e c o n d será la cola que ya está en 'orden de pila'.Fi r s ts e c o n dFi r s ts e c o n d
- El empuje se realiza simplemente colocando el parámetro en cola en .Fi r s t
- El estallido se realiza de la siguiente manera. Si está vacío, simplemente retiramos el s e c o n d y devolvemos el resultado. De lo contrario, revertimosFi r s ts e c o n d , anexar todos de s e c o n d a f i r s t y de intercambio f i r s t y s e c o n d . Luego retiramos s e c oFi r s ts e c o n dFi r s tFi r s ts e c o n d y devolver el resultado de la retirada de cola.s e c o n d
Código C # para el primer algoritmo
Esto debería ser bastante legible, incluso si nunca antes has visto C #. Si no sabe qué son los genéricos, simplemente reemplace todas las instancias de 'T' por 'cadena' en su mente, para una pila de cadenas.
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();
}
}
}
Análisis
Obviamente, Push funciona en el tiempo . Pop puede tocar todo dentro de f i r s t y s e c o n d una cantidad constante de veces, por lo que tenemos O ( n ) en el peor caso. El algoritmo exhibe este comportamiento (por ejemplo) si uno empuja n elementos en la pila y luego realiza repetidamente una sola operación Push y una sola operación Pop en sucesión.O ( 1 )Fi r s ts e c o n dO ( n )norte
El segundo algoritmo
Tenemos dos colas: cola y cola s e c o n d . f i r s t será nuestra 'cola de empuje', mientras que s e c o n d será la cola que ya está en 'orden de pila'.Fi r s ts e c o n dFi r s ts e c o n d
Esta es una versión adaptada del primer algoritmo, en el que no "barajamos" inmediatamente el contenido de en s e c o nFi r s t . En cambio, si f i r s t contiene un número suficientemente pequeño de elementos en comparación con s e c o n d (es decir, la raíz cuadrada del número de elementos en s e c o n d ), solo reorganizamos f i r s t en orden de pila y no lo fusioness e c o n dFi r s ts e c o n ds e c o n dFi r s t .s e c o n d
- Se sigue presionando simplemente colocando el parámetro en cola en .Fi r s t
- El estallido se realiza de la siguiente manera. Si está vacío, simplemente retiramos el s e c o n d y devolvemos el resultado. De lo contrario, reorganizamos los contenidos de f i r s t para que estén en orden de pila. Si | f i r s t | < √Fi r s ts e c o n dFi r s tsimplemente retiramosfirsty devolvemos el resultado. De lo contrario, añadimossecondaEl | Fi r s t | < | s e c o n dEl |-------√Fi r s ts e c o n d , intercambiamos f i r s t y s e c o n d , retiramos de la cola s e c o n d y devolvemos el resultado.Fi r s tFi r s ts e c o n ds e c o n d
Código C # para el primer algoritmo
Esto debería ser bastante legible, incluso si nunca antes has visto C #. Si no sabe qué son los genéricos, simplemente reemplace todas las instancias de 'T' por 'cadena' en su mente, para una pila de cadenas.
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();
}
}
}
}
Análisis
Obviamente, Push funciona en el tiempo .O ( 1 )
Pop funciona en tiempo amortizado. Hay dos casos: si| first| < √O ( n--√)El | Fi r s t | < | s e c o n dEl |-------√, luego barajamos en orden de pila en O ( | f i r s t | ) = O ( √Fi r s ttiempo. Si| first| ≥ √O ( |fi r s t | ) = O ( n--√), entonces debemos haber tenido al menos√El | First|≥|second|−−−−−−−√ llama a Push. Por lo tanto, solo podemos golpear este caso cada √n−−√ llamadas a Push y Pop. El tiempo real de ejecución para este caso esO(n), por lo que el tiempo amortizado esO( nn−−√O(n).O(nn√)=O(n−−√)
Nota final
Es posible eliminar la variable adicional a costa de hacer Pop an operación, haciendo que Pop reorganicefirsten cada llamada en lugar de hacer que Push haga todo el trabajo.O(n−−√)first