J'ai la question de devoirs suivante:
Implémentez les méthodes de pile push (x) et pop () à l'aide de deux files d'attente.
Cela me semble étrange parce que:
- Une pile est une file d'attente (LIFO)
- Je ne vois pas pourquoi vous auriez besoin de deux files d'attente pour le mettre en œuvre
J'ai cherché autour de:
et trouvé quelques solutions. C'est ce que j'ai fini avec:
public class Stack<T> {
LinkedList<T> q1 = new LinkedList<T>();
LinkedList<T> q2 = new LinkedList<T>();
public void push(T t) {
q1.addFirst(t);
}
public T pop() {
if (q1.isEmpty()) {
throw new RuntimeException(
"Can't pop from an empty stack!");
}
while(q1.size() > 1) {
q2.addFirst( q1.removeLast() );
}
T popped = q1.pop();
LinkedList<T> tempQ = q1;
q1 = q2;
q2 = tempQ;
return popped;
}
}
Mais je ne comprends pas l’avantage de l’utilisation d’une seule file; la version à deux files d'attente semble inutilement compliquée.
Supposons que nous choisissions les poussées comme étant les plus efficaces des 2 (comme je l’ai fait ci-dessus), push
resterions les mêmes et pop
exigeraient simplement une itération au dernier élément, puis le retourner. Dans les deux cas, le push
serait O(1)
et le pop
serait O(n)
; mais la version à file d'attente unique serait considérablement plus simple. Cela ne devrait nécessiter qu'une seule boucle for.
Est-ce que je manque quelque chose? Toute idée ici serait appréciée.
Réponses:
Il n'y a aucun avantage: c'est un exercice purement académique.
Il y a très longtemps, lorsque j'étais étudiant de première année à l'université, j'avais un exercice similaire 1 . L'objectif était d'enseigner aux étudiants comment utiliser la programmation orientée objet pour mettre en œuvre des algorithmes au lieu d'écrire des solutions itératives à l'aide de
for
boucles avec des compteurs de boucles. Combinez et réutilisez les structures de données existantes pour atteindre vos objectifs.Vous n'utiliserez jamais ce code dans Real World TM . Ce que vous devez retenir de cet exercice, c'est comment "sortir des sentiers battus" et réutiliser le code.
Veuillez noter que vous devriez utiliser l' interface java.util.Queue dans votre code au lieu d'utiliser directement l'implémentation:
Cela vous permet d’utiliser d’autres
Queue
implémentations si vous le souhaitez, ainsi que de masquer 2 méthodesLinkedList
qui peuvent contourner l’esprit de l’Queue
interface. Cela inclutget(int)
etpop()
(pendant que votre code est compilé, il y a une erreur de logique là-dedans étant donné les contraintes de votre affectation. Déclarer vos variables commeQueue
au lieu deLinkedList
le révélera). Lecture connexe: Comprendre «programmer pour une interface» et Pourquoi les interfaces sont-elles utiles?1 Je me souviens encore: l’exercice consistait à inverser une pile en utilisant uniquement des méthodes de l’interface de pile et aucune méthode d’utilité ni aucune
java.util.Collections
autre classe d’utilitaire "statique uniquement". La solution correcte consiste à utiliser d'autres structures de données en tant qu'objets de stockage temporaires: vous devez connaître les différentes structures de données, leurs propriétés et comment les combiner pour le faire. Stumped la plupart de ma classe CS101 qui n'avait jamais programmé auparavant.2 Les méthodes sont toujours là, mais vous ne pouvez pas y accéder sans transformations de caractères ou réflexions. Il n'est donc pas facile d'utiliser ces méthodes sans file d'attente.
la source
Il n'y a pas d'avantage. Vous avez correctement compris que l’utilisation de files d’attente pour implémenter une pile entraîne une complexité horrible en termes de temps. Aucun programmeur (compétent) ne ferait jamais quelque chose comme ça dans la "vraie vie".
Mais c'est possible. Vous pouvez utiliser une abstraction pour en implémenter une autre, et inversement. Une pile peut être implémentée en termes de deux files d'attente. Vous pouvez également implémenter une file d'attente en termes de deux piles. L'avantage de cet exercice est:
En fait, c’est un excellent exercice. Je devrais le faire moi-même maintenant :)
la source
push
,peek
et lespop
opérations sont en O (1). Il en va de même pour une pile redimensionnable basée sur un tableau, à l'exception du fait qu'ellepush
est dans O (1) et qu'elle est amortie, avec le cas le plus défavorable de O (n). Comparé à cela, une pile en file d'attente est très inférieure avec O (n) push, O (1) pop et peek, ou bien O (1) push, O (n) pop et peek.Il est vraiment utile de créer une file d’attente sur deux piles. Si vous utilisez des structures de données immuables à partir d'un langage fonctionnel, vous pouvez insérer une pile d'éléments pouvant être déplacés et extraire une liste d'éléments pouvant être masqués. Les éléments amovibles sont créés lorsque tous les éléments ont été extraits, et la nouvelle pile extensible est l'inverse de la pile extensible, où la nouvelle pile extensible est maintenant vide. C'est efficace.
En ce qui concerne une pile composée de deux files d'attente? Cela peut sembler logique dans un contexte où vous avez un grand nombre de files d’attente rapides et volumineuses. C'est définitivement inutile en tant qu'exercice sur Java. Mais cela peut avoir un sens s'il s'agit de canaux ou de files d'attente de messagerie. (ie: N messages mis en file d'attente, avec une opération O (1) pour déplacer (N-1) les éléments à l'avant dans une nouvelle file d'attente.)
la source
L'exercice est inutilement conçu d'un point de vue pratique. Le but est de vous obliger à utiliser l'interface de la file d'attente de manière intelligente pour implémenter la pile. Par exemple, votre solution "Une file d'attente" nécessite que vous effectuiez une itération sur la file d'attente pour obtenir la dernière valeur d'entrée pour l'opération "pop" de pile. Cependant, une structure de données de file d'attente ne permet pas de parcourir les valeurs, vous êtes limité à y accéder dans le premier entré, premier sorti (FIFO).
la source
Comme d'autres l'ont déjà fait remarquer: il n'y a pas d'avantage réel.
Quoi qu'il en soit, une réponse à la deuxième partie de votre question, pourquoi ne pas simplement utiliser une file d'attente, est au-delà de Java.
En Java même le
Queue
interface a unesize()
méthode et toutes les implémentations standard de cette méthode sont O (1).Ce n'est pas nécessairement vrai pour la liste chaînée naïve / canonique comme le ferait un programmeur C / C ++, qui garderait simplement les pointeurs sur le premier et le dernier élément et chaque élément un pointeur sur l'élément suivant.
Dans ce cas,
size()
c’est O (n) et devrait être évité en boucle. Ou la mise en œuvre est opaque et ne fournit que le strict minimumadd()
etremove()
.Avec une telle implémentation, vous devez d’abord compter le nombre d’éléments en les transférant dans la deuxième file, transférer
n-1
à la première file d'attente et renvoyer l'élément restant.Cela dit, cela ne constituerait probablement pas une telle chose si vous vivez à Java-land.
la source
size()
méthode. Cependant, en l'absence d'unesize()
méthode O (1) , il est trivial pour la pile de garder une trace de sa taille actuelle. Rien ne peut arrêter une implémentation à une file d'attente.Il est difficile d'imaginer une utilisation pour une telle implémentation, c'est vrai. Mais l' essentiel est de prouver que cela peut être fait .
En termes d'utilisations réelles de ces choses, cependant, je peux penser à deux. Une des utilisations possibles consiste à implémenter des systèmes dans des environnements soumis à des contraintes qui ne sont pas conçus : par exemple, les blocs redstone de Minecraft s’avèrent être un système complet de Turing, permettant aux utilisateurs d’implémenter des circuits logiques et même des processeurs entiers. Au tout début du jeu basé sur un script, beaucoup des premiers robots de jeu étaient également implémentés de cette façon.
Mais vous pouvez également appliquer ce principe à l’inverse, en veillant à ce que quelque chose ne soit pas possible dans un système alors que vous ne le souhaitez pas . Cela peut se produire dans des contextes de sécurité: par exemple, des systèmes de configuration puissants peuvent être un atout, mais il existe toujours des degrés de puissance que vous préféreriez peut-être ne pas donner aux utilisateurs. Cela limite ce que vous pouvez permettre au langage de configuration de faire, de peur qu'il ne soit renversé par un attaquant, mais dans ce cas, c'est ce que vous voulez.
la source