Support des 4 séances du chapitre « Structures de données : piles et files ». Il sert à la fois de support de
projection en classe et de support de révision à la maison : cliquez sur « Voir la réponse » sous chaque exercice
pour afficher le corrigé.
Type abstrait de données (TAD) : on distingue toujours l'interface (les
opérations disponibles) de l'implémentation (comment elles sont codées). Une pile est LIFO
(Last In, First Out), une file est FIFO (First In, First Out) — même vocabulaire d'opérations, politique d'accès
opposée.
Séance 1 — Découvrir les listes chaînées
TAD, deux implémentations, et un bug classique à débusquer
1 Version fonctionnelle (tuples)
vide = None
def cons(v, reste): return (v, reste)
def car(liste): return liste[0]
def cdr(liste): return liste[1]
creer_liste_numerique(tableau), afficher(liste), nieme_element(liste, n) :
Voir la réponse
def creer_liste_numerique(tableau):
liste = vide
for x in reversed(tableau):
liste = cons(x, liste)
return liste
def afficher(liste):
s = "["
while liste is not vide:
s += str(car(liste))
liste = cdr(liste)
if liste is not vide: s += ", "
print(s + "]")
def nieme_element(liste, n):
for _ in range(n):
liste = cdr(liste)
return car(liste)
nieme_element est en O(n), contrairement à l'accès direct tab[n] d'un tableau (O(1)).
2 Version orientée objet (Cellule)
class Cellule:
def __init__(self, valeur, suivant=None):
self.valeur = valeur
self.suivant = suivant
🐛 Le bug d'auto-référencement (Maillon mutable)
Version buguée :
def renverser_bug(tete):
precedent = None
actuel = tete
while actuel is not None:
actuel.suivant = precedent
precedent = actuel
actuel = actuel.suivant # vient d'être écrasé !
return precedent
Voir la réponse (version corrigée)
def renverser(tete):
precedent = None
actuel = tete
while actuel is not None:
suivant_sauvegarde = actuel.suivant # sauvegarde AVANT d'écraser
actuel.suivant = precedent
precedent = actuel
actuel = suivant_sauvegarde
return precedent
Toujours sauvegarder une référence dont on aura encore besoin AVANT de l'écraser sur un objet mutable.
Séance 2 — Manipuler des listes chaînées
Exercices progressifs : occurrences, rang, tri, conversion
1-2 occurrences / trouver_rang
Voir la réponse
def occurrences(liste, valeur):
if liste is vide:
return 0
return (1 if car(liste) == valeur else 0) + occurrences(cdr(liste), valeur)
def trouver_rang(liste, valeur, rang=0):
if liste is vide:
return -1
if car(liste) == valeur:
return rang
return trouver_rang(cdr(liste), valeur, rang + 1)
3 renverser_efficace — technique de l'accumulateur
Voir la réponse
def renverser_efficace(liste):
resultat = vide
while liste is not vide:
resultat = cons(car(liste), resultat)
liste = cdr(liste)
return resultat
En O(n), contrairement à une version par concaténations successives qui serait en O(n²).
4-5 inserer_triee / tri_par_insertion
Voir la réponse
def inserer_triee(liste, valeur):
if liste is vide or valeur <= car(liste):
return cons(valeur, liste)
return cons(car(liste), inserer_triee(cdr(liste), valeur))
def tri_par_insertion(liste):
resultat = vide
while liste is not vide:
resultat = inserer_triee(resultat, car(liste))
liste = cdr(liste)
return resultat
6 liste_vers_tableau
Voir la réponse
def liste_vers_tableau(liste):
tableau = []
while liste is not vide:
tableau.append(car(liste))
liste = cdr(liste)
return tableau
🎯 Défi bonus — sont_identiques
Comparer deux listes chaînées, valeur par valeur.
Séance 3 — Les piles (LIFO)
Classe complète, historique de navigation, calculatrice RPN
1 Classe Pile (avec taille() optimisée)
Voir la réponse
class Pile:
def __init__(self):
self.sommet = None
self._taille = 0
def est_vide(self):
return self.sommet is None
def empiler(self, valeur):
self.sommet = Maillon(valeur, self.sommet)
self._taille += 1
def depiler(self):
valeur = self.sommet.valeur
self.sommet = self.sommet.suivant
self._taille -= 1
return valeur
def consulter(self):
return self.sommet.valeur
def vider(self):
self.sommet = None
self._taille = 0
def taille(self):
return self._taille # O(1) grâce au compteur maintenu à jour
3 Calculatrice en notation polonaise inversée (RPN)
"3 4 +" = 7, "5 1 2 + 4 * + 3 -" = 14
Voir la réponse
def calculer_rpn(expression):
pile = Pile()
for jeton in expression.split():
if jeton in ("+", "-", "*", "/"):
b = pile.depiler(); a = pile.depiler()
if jeton == "+": r = a + b
elif jeton == "-": r = a - b
elif jeton == "*": r = a * b
else: r = a / b
pile.empiler(r)
else:
pile.empiler(float(jeton))
return pile.depiler()
Attention à l'ordre : b est dépilé AVANT a, crucial pour - et /.
🎯 Défi bonus — Annuler / Rétablir
Une classe EditeurTexte (ecrire/annuler/retablir) sur le même principe à deux piles que le navigateur.
Séance 4 — Piles avancées + Les files (FIFO)
Parenthèses équilibrées, et la seconde structure linéaire du programme
1 parentheses_equilibrees
Voir la réponse
def parentheses_equilibrees(expression):
pile = Pile()
paires = {')': '(', ']': '[', '}': '{'}
for car in expression:
if car in "([{":
pile.empiler(car)
elif car in ")]}":
if pile.est_vide() or pile.depiler() != paires[car]:
return False
return pile.est_vide()
2 Classe File (tête + queue, tout en O(1))
Voir la réponse
class File:
def __init__(self):
self.tete = None
self.queue = None
self._taille = 0
def est_vide(self):
return self.tete is None
def enfiler(self, valeur):
nouveau = Maillon(valeur)
if self.est_vide():
self.tete = nouveau
self.queue = nouveau
else:
self.queue.suivant = nouveau
self.queue = nouveau
self._taille += 1
def defiler(self):
valeur = self.tete.valeur
self.tete = self.tete.suivant
if self.tete is None:
self.queue = None
self._taille -= 1
return valeur
def premier(self):
return self.tete.valeur
def taille(self):
return self._taille
Sans la référence queue, enfiler() devrait parcourir toute la file : O(n) au lieu de O(1).
3 Le jeu de la patate chaude
Voir la réponse
def patate_chaude(noms, k):
file = File()
for nom in noms:
file.enfiler(nom)
while file.taille() > 1:
for _ in range(k):
file.enfiler(file.defiler())
elimine = file.defiler()
print(f"{elimine} est éliminé !")
return file.premier()
file.enfiler(file.defiler()) fait "tourner" un élément de la tête vers la queue sans le perdre.
Bilan du chapitre — Pile (LIFO) : pile d'appels récursifs, annuler/rétablir, historique de navigation, parenthésage, RPN. File (FIFO) : files d'attente, impression, traitement dans l'ordre d'arrivée, et (l'an prochain) parcours en largeur (BFS) d'un graphe.
🎯 Défi bonus — Calculatrice avec priorités
Évaluer "3 + 4 * 2" = 11 (et non 14), en respectant les priorités, avec deux piles.
Voir la réponse
def calculer_expression(expression):
priorite = {'+': 1, '-': 1, '*': 2, '/': 2}
valeurs = Pile()
operateurs = Pile()
def appliquer():
b = valeurs.depiler(); a = valeurs.depiler()
op = operateurs.depiler()
if op == '+': valeurs.empiler(a + b)
elif op == '-': valeurs.empiler(a - b)
elif op == '*': valeurs.empiler(a * b)
else: valeurs.empiler(a / b)
for jeton in expression.split():
if jeton in priorite:
while (not operateurs.est_vide() and operateurs.consulter() in priorite
and priorite[operateurs.consulter()] >= priorite[jeton]):
appliquer()
operateurs.empiler(jeton)
else:
valeurs.empiler(float(jeton))
while not operateurs.est_vide():
appliquer()
return valeurs.depiler()