La réponse ci-dessous est "tricher", en ce sens qu'elle n'utilise pas d'espace entre les opérations mais les opérations elles-mêmes peuvent utiliser plus que l' espace . Voir ailleurs dans ce fil pour une réponse qui n'a pas ce problème.O(1)
Bien que je n’aie pas de réponse à votre question exacte, j’ai trouvé un algorithme qui fonctionne en temps au lieu de . Je crois que c'est serré, bien que je n'ai pas de preuve. Si quelque chose se passe, l'algorithme montre qu'essayer de prouver une borne inférieure de est futile, cela pourrait donc aider à répondre à votre question.O(n)O(n)O(n−−√)O(n)O(n)
Je présente deux algorithmes, le premier étant un algorithme simple avec un temps d'exécution pour Pop et le second avec un temps d'exécution pour Pop. Je décris le premier principalement en raison de sa simplicité, de sorte que le second est plus facile à comprendre.O ( √O(n)O(n−−√)
Pour être plus précis: le premier n’utilise pas d’espace supplémentaire, a un push pire (et amorti) et un pop (et amorti), mais le pire comportement n’est pas toujours déclenché. Dans la mesure où elle n'utilise pas d'espace supplémentaire au-delà des deux files d'attente, elle est légèrement "meilleure" que la solution proposée par Ross Snider.O ( n )O(1)O(n)
La seconde utilise un seul champ entier (donc espace supplémentaire), a un cas le plus défavorable (et amorti) et un amorti Pop. Son temps de fonctionnement est donc nettement meilleur que celui de l'approche «simple», mais il utilise encore un peu d'espace supplémentaire.O ( 1 ) O ( √O(1)O(1)O(n−−√)
Le premier algorithme
Nous avons deux files d'attente: file d'attente en et file d'attente . sera notre "file d'attente", le sera la file déjà en ordre de pile.s e c o n d f i r s t s e c o n dfirstsecondfirstsecond
- Pousser se fait par enqueueing simplement le paramètre sur .first
- Le saut est fait comme suit. Si est vide, nous retirons simplement les et renvoyons le résultat. Sinon, on inverse en , on ajoute tout le au et on échange les et . Nous retirons ensuite et renvoyons le résultat de la file d'attente.s e c o n dfirstseconds e c o n d f i r s t f i r s t s e c o n d s e c o n dfirstsecondfirstfirstsecondsecond
Code C # pour le premier algorithme
Cela pourrait être assez lisible, même si vous n’avez jamais vu C # auparavant. Si vous ne savez pas ce que sont les génériques, remplacez toutes les occurrences de «T» par «chaîne» dans votre esprit, pour une pile de chaînes.
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();
}
}
}
Une analyse
De toute évidence, Push fonctionne dans le temps . Pop peut toucher tout ce qui est à l'intérieur en et en un nombre de fois constant, nous avons donc dans le pire des cas. L’algorithme présente ce comportement (par exemple) si l’on place éléments sur la pile, puis exécute de manière répétée une opération Push unique et une seule opération Pop successivement.f i r s t s e c o n d O ( n ) nO(1)firstsecondO(n)n
Le deuxième algorithme
Nous avons deux files d'attente: file d'attente en et file d'attente . sera notre "file d'attente", le sera la file déjà en ordre de pile.firstsecondfirstsecond
Il s'agit d'une version adaptée du premier algorithme, dans laquelle nous ne «brassons» pas immédiatement le contenu de à . Au lieu de cela, si contient un nombre suffisamment petit d’éléments par rapport à (à savoir la racine carrée du nombre d’éléments en ), nous ne le réorganisons qu’en dans l’ordre de pile et ne le fusionnons pas avec .firstsecondfirstsecondsecondfirstsecond
- Pousser est toujours fait par enqueueing simplement le paramètre sur .first
- Le saut est fait comme suit. Si est vide, nous retirons simplement les et renvoyons le résultat. Sinon, nous réorganisons le contenu de afin qu'il soit placé dans l'ordre de la pile. Si nous avons simplement dequeue et renvoie le résultat. Sinon, nous ajoutons le au , permutons le et le , retirons la et renvoyons le résultat.firstsecondfirst|first|<|second|−−−−−−−√firstsecondfirstfirstsecondsecond
Code C # pour le premier algorithme
Cela pourrait être assez lisible, même si vous n’avez jamais vu C # auparavant. Si vous ne savez pas ce que sont les génériques, remplacez toutes les occurrences de «T» par «chaîne» dans votre esprit, pour une pile de chaînes.
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();
}
}
}
}
Une analyse
De toute évidence, Push fonctionne dans le temps .O(1)
Pop fonctionne en temps amorti . Il y a deux cas: if , puis nous mélangeons d' dans l'ordre des piles dans le temps . Si , nous devons avoir reçu au moins appels Push. Par conséquent, nous ne pouvons frapper ce cas que tous les appels à Push et Pop. Le temps d'exécution réel pour ce cas est , le temps amorti est donc .O(n−−√)|first|<|second|−−−−−−−√firstO(|first|)=O(n−−√)|first|≥|second|−−−−−−−√n−−√n−−√O(n)O(nn√)=O(n−−√)
Note finale
Il est possible d’éliminer la variable supplémentaire au prix de la transformation Pop en une opération , en ayant Pop réorganisé d’ à chaque appel au lieu de laisser Push effectuer tout le travail.O(n−−√)first