A resposta abaixo é 'trapaça', pois, embora não use espaço entre operações, as próprias operações podem usar mais que espaço. Veja em outro lugar neste tópico uma resposta que não tenha esse problema.O(1)
Embora eu não tenha uma resposta para sua pergunta exata, encontrei um algoritmo que funciona em tempo em vez deO(n). Eu acredito que isso é apertado, embora eu não tenha uma prova. De qualquer forma, o algoritmo mostra que tentar provar um limite inferior deO(n)é inútil, portanto, pode ajudar a responder sua pergunta.O(n−−√)O(n)O(n)
Apresento dois algoritmos, o primeiro sendo um algoritmo simples com um tempo de execução de para Pop e o segundo com um O ( √O(n)tempo de execução do Pop. Descrevo o primeiro, principalmente por sua simplicidade, para facilitar o entendimento do segundo.O(n−−√)
Para dar mais detalhes: o primeiro não usa espaço adicional, possui um Push pior caso (e amortizado) e O ( n ) O pior caso (e amortizado), mas o comportamento do pior caso nem sempre é acionado. Como não usa espaço adicional além das duas filas, é um pouco "melhor" do que a solução oferecida por Ross Snider.O(1)O(n)
O segundo usa um único campo inteiro (portanto, espaço extra), possui O ( 1 ) pior caso (e amortizado) Push e O ( √O(1)O(1)Pop amortizado. Portanto, o tempo de execução é significativamente melhor do que o da abordagem "simples", mas usa algum espaço extra.O(n−−√)
O primeiro algoritmo
Temos duas filas: fila de e fila s e c o n d . f i r s t será a 'fila push', enquanto s e c o n d será a fila já na 'ordem pilha'.firstsecondfirstsecond
- O envio é feito simplesmente enfileirando o parâmetro em .first
- O popping é feito da seguinte maneira. Se está vazio, que simplesmente Desenfileiramento s e c o n d e devolve o resultado. Caso contrário, se inverter f i r s t , acrescentar tudo de s e c o n d a f i r s t e de troca de f i r s t e s e c o n d . Em seguida, desenfileiramos s e c ofirstsecondfirsts e c o n dfi r s tfi r s ts e c o n d e devolver o resultado do retirar da fila.s e c o n d
Código C # para o primeiro algoritmo
Isso pode ser bem legível, mesmo que você nunca tenha visto C # antes. Se você não souber o que são genéricos, substitua todas as instâncias de 'T' por 'string' em sua mente, por uma pilha de strings.
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álise
Obviamente, o Push funciona no tempo . Pop pode tocar tudo dentro f i r s t e s e c o n d uma quantidade constante de tempos, de modo que temos S ( n ) no pior dos casos. O algoritmo exibe esse comportamento (por exemplo) se alguém colocar n elementos na pilha e executar repetidamente uma única operação Push e uma única operação Pop em sucessão.O ( 1 )fi r s ts e c o n dO ( n )n
O segundo algoritmo
Temos duas filas: fila de e fila s e c o n d . f i r s t será a 'fila push', enquanto s e c o n d será a fila já na 'ordem pilha'.fi r s ts e c o n dfi r s ts e c o n d
Esta é uma versão adaptada do primeiro algoritmo, em que não faça imediatamente 'baralhamento' o conteúdo de a s e c o n d . Em vez disso, se f i r s t contém um número suficientemente pequeno de elementos em comparação com s e c o n d (ou seja, a raiz quadrada do número de elementos em s e c o n d ), apenas reorganizamos f i r s t em ordem de pilha e não a mescle comfi r s ts 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
- A pressão ainda é feita simplesmente enfileirando o parâmetro em .fi r s t
- O popping é feito da seguinte maneira. Se está vazio, que simplesmente Desenfileiramento s e c o n d e devolve o resultado. Caso contrário, reorganizamos o conteúdo de f i r s t para que eles estejam na ordem das pilhas. Se | f i r s t | < √fi r s ts e c o n dfi r s tsimplesmente desenfileiramosfirsteretornamos o resultado. Caso contrário, anexamossecondemfirst, permutafirstesecond, Desenfileiramentoseconde devolve o resultado.| first|<|second|−−−−−−−√fi r s ts e c o n dfi r s tfi r s ts e c o n ds e c o n d
Código C # para o primeiro algoritmo
Isso pode ser bem legível, mesmo que você nunca tenha visto C # antes. Se você não souber o que são genéricos, substitua todas as instâncias de 'T' por 'string' em sua mente, por uma pilha de strings.
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álise
Obviamente, o Push funciona no tempo .O ( 1 )
O pop trabalha em tempo amortizado. Existem dois casos: se| first| < √O ( n--√), então embaralhamosfirstna ordem da pilha emO(|first|)=O(√| first|<|second|−−−−−−−√firsttempo. Se| first| ≥ √O(|first|)=O(n−−√), então devemos ter pelo menos√|first|≥|second|−−−−−−−√ pede Push. Portanto, podemos apenas atingir esse caso a cada √n−−√ chama para Push e Pop. O tempo de execução real para este caso éO(n), portanto, o tempo amortizado éO( n)n−−√O(n).O(nn√)=O(n−−√)
Nota final
É possível eliminar a variável extra ao custo de tornar Pop um operação, por ter Pop reorganizarfirstem cada chamada, em vez de ter push fazer todo o trabalho.O(n−−√)first