Retour à la page d'accueilNSI · Terminale (spé maths) · Changer de matière
Commencer gratuitement
Terminale (spé maths) · NSI
Structures de données, bases de données et algorithmique · Structures de données

Piles, files et arbres binaires

Dans une salle d'attente d'urgences, on passe dans l'ordre d'arrivée. Dans ton navigateur, le bouton « Retour » te ramène à la dernière page visitée. Ces deux situations ressemblent à des listes, mais elles ne se comportent pas pareil. Comment un programme peut-il imposer l'un ou l'autre ordre ? Et comment ranger des données pour les retrouver vite ? C'est l'objet de cette page.

Piles : dernier arrivé, premier sorti

Une pile est une structure de données où l'on n'accède qu'à un seul élément : le sommet. Comme une pile d'assiettes, on pose une assiette sur le dessus et on reprend celle du dessus. On dit que la pile suit le principe LIFO (Last In, First Out).

L'interface d'une pile se réduit à quelques méthodes :

Méthode Effet
empiler(x) ajoute x au sommet
depiler() retire et renvoie l'élément du sommet
est_vide() renvoie True si la pile est vide
Une pile contenant 4, 7 et 2 avec 2 au sommet ; empiler ajoute au sommet, dépiler retire le sommet. 4 7 2 fond de la pile ← sommet empiler(x) ↓ ↑ depiler()
Entraîne-toi★☆☆Pas commencé
On considère une pile initialement vide. On effectue successivement les opérations suivantes : empiler(4), empiler(7), empiler(2), depiler(), empiler(9), depiler(). a)…

On considère une pile initialement vide. On effectue successivement les opérations suivantes : empiler(4), empiler(7), empiler(2), depiler(), empiler(9), depiler().

  1. a) Donner le contenu de la pile après chaque opération (le sommet étant indiqué).
  1. b) Donner la valeur renvoyée par chacun des deux appels à depiler().
  1. c) Indiquer le principe (LIFO ou FIFO) suivi par une pile.
Résoudre

Application : vérifier un parenthésage

Pour savoir si ((3+2)*(4-1)) est bien parenthésée, on lit l'expression de gauche à droite :

  • parenthèse ouvrante : on l'empile ;

  • parenthèse fermante : si la pile est vide, erreur ; sinon on dépile ;

  • à la fin, l'expression est bien parenthésée si et seulement si la pile est vide.

Le dernier ( ouvert doit être le premier refermé : c'est exactement le principe LIFO.

Entraîne-toi★★☆Pas commencé
On souhaite vérifier qu'une expression contenant des parenthèses, par exemple ((3+2)(4-1)), est bien parenthésée, à l'aide d'une pile. a) Décrire l'algorithme : que…

On souhaite vérifier qu'une expression contenant des parenthèses, par exemple ((3+2)*(4-1)), est bien parenthésée, à l'aide d'une pile.

  1. a) Décrire l'algorithme : que fait-on à la lecture d'une parenthèse ouvrante, puis d'une parenthèse fermante ? Quelle condition finale garantit que l'expression est bien parenthésée ?
  1. b) Appliquer l'algorithme à ((3+2)*(4-1)) en donnant la taille de la pile après la lecture de chaque parenthèse.
  1. c) Appliquer l'algorithme à (5*(2+1))*3) et indiquer à quel moment l'erreur est détectée.
Résoudre

Files : premier arrivé, premier sorti

Une file suit le principe FIFO (First In, First Out) : on ajoute à la queue et on retire à la tête, comme à la caisse d'un supermarché.

Méthode Effet
enfiler(x) ajoute x à la queue
defiler() retire et renvoie l'élément de la tête
est_vide() renvoie True si la file est vide
Une file contenant Anna, Hugo et Léa : on défile par la tête à gauche et on enfile par la queue à droite. Anna Hugo Léa tête queue ← defiler() enfiler(x) ←

Implémentation en Python

Une même interface peut avoir plusieurs implémentations. Voici une file écrite avec une liste Python comme attribut :

class File:

    def __init__(self):

        self.elements = []

    def enfiler(self, x):

        self.elements.append(x)

    def defiler(self):

        return self.elements.pop(0)

    def est_vide(self):

        return len(self.elements) == 0

L'utilisateur de la classe n'a besoin que de l'interface : il ne sait pas (et n'a pas à savoir) que c'est une liste qui est cachée à l'intérieur.

Entraîne-toi★☆☆Pas commencé
On considère une file initialement vide. On effectue successivement : enfiler("Anna"), enfiler("Hugo"), enfiler("Léa"), defiler(), enfiler("Paul"), defiler(). a) Donner…

On considère une file initialement vide. On effectue successivement : enfiler("Anna"), enfiler("Hugo"), enfiler("Léa"), defiler(), enfiler("Paul"), defiler().

  1. a) Donner le contenu de la file après chaque opération (en précisant la tête et la queue).
  1. b) Donner les valeurs renvoyées par les deux appels à defiler().
  1. c) Indiquer le principe (LIFO ou FIFO) suivi par une file.
Résoudre
Entraîne-toi★★☆Pas commencé
Dans un service d'urgences de l'hôpital de Lille, les patients sont pris en charge dans l'ordre d'arrivée. On modélise la salle d'attente par une file. Arrivent dans…

Dans un service d'urgences de l'hôpital de Lille, les patients sont pris en charge dans l'ordre d'arrivée. On modélise la salle d'attente par une file. Arrivent dans l'ordre : Martin, Dubois, Petit, Roux. Martin est pris en charge, puis Lemaire arrive, puis deux patients sont pris en charge.

  1. a) Donner l'état de la file à la fin.
  1. b) Indiquer quels patients ont été pris en charge, dans l'ordre.
  1. c) Écrire en Python une classe File avec les méthodes enfiler, defiler et est_vide, en utilisant une liste Python comme attribut.
Résoudre

Une file avec deux piles

On peut aussi implémenter une file avec deux piles entree et sortie. On enfile dans entree. Pour défiler : si sortie est vide, on y transfère tout entree (ce qui inverse l'ordre), puis on dépile sortie. L'inversion de l'inversion rétablit l'ordre d'arrivée : c'est ce qui garantit le FIFO.

Un défilement peut coûter $n$ opérations, mais chaque élément n'est transféré qu'une seule fois : pour $n$ éléments enfilés puis défilés, le coût total reste proportionnel à $n$.

Entraîne-toi★★★Pas commencé
On implémente une file à l'aide de deux piles entree et sortie. Pour enfiler, on empile dans entree. Pour défiler, si sortie est vide, on transfère tous les éléments de…

On implémente une file à l'aide de deux piles entree et sortie. Pour enfiler, on empile dans entree. Pour défiler, si sortie est vide, on transfère tous les éléments de entree vers sortie (en dépilant puis empilant), puis on dépile sortie.

  1. a) Exécuter la séquence : enfiler 1, 2, 3 ; défiler ; enfiler 4 ; défiler ; défiler. Donner l'état des deux piles après chaque opération.
  1. b) Montrer que l'ordre FIFO est bien respecté.
  1. c) Un défilement peut coûter $n$ opérations élémentaires. Montrer néanmoins que, pour $n$ éléments enfilés puis tous défilés, le coût total est en $O(n)$.
Résoudre

Arbres binaires

Une liste est linéaire. Pour représenter une hiérarchie (dossiers, arbre généalogique), on utilise un arbre. Dans un arbre binaire, chaque nœud a au plus deux fils : un fils gauche et un fils droit.

Arbre binaire de racine A, de fils B et C, avec D, E et F pour feuilles. A B C D E F

Encadrement de la hauteur

Un arbre de hauteur $h$ a au moins un nœud par niveau, et au plus $2^{k-1}$ nœuds au niveau $k$. Donc

$$h \le n \le 1 + 2 + 4 + \dots + 2^{h-1} = 2^h - 1.$$

On en déduit $h \ge \log_2(n+1)$, donc $h \ge \lceil \log_2(n+1) \rceil$. Un arbre « bien rempli » est donc très peu haut : 1 023 nœuds tiennent sur 10 niveaux.

Entraîne-toi★☆☆Pas commencé
On considère l'arbre binaire suivant : la racine est A ; A a pour fils gauche B et pour fils droit C ; B a pour fils gauche D et pour fils droit E ; C a pour unique fils…

On considère l'arbre binaire suivant : la racine est A ; A a pour fils gauche B et pour fils droit C ; B a pour fils gauche D et pour fils droit E ; C a pour unique fils droit F ; D, E et F sont des feuilles.

  1. a) Donner la taille de cet arbre.
  1. b) Donner sa hauteur (en convenant que la racine seule a une hauteur de 1).
  1. c) Donner la liste des feuilles.
Résoudre

Parcours d'un arbre binaire

Parcourir un arbre, c'est visiter chaque nœud une fois. Trois parcours en profondeur (récursifs) existent, selon le moment où l'on traite la racine par rapport à ses sous-arbres :

Parcours Ordre
préfixe racine, sous-arbre gauche, sous-arbre droit
infixe sous-arbre gauche, racine, sous-arbre droit
suffixe sous-arbre gauche, sous-arbre droit, racine

Le parcours en largeur visite les nœuds niveau par niveau, de gauche à droite. Il s'écrit avec une file : on enfile la racine ; tant que la file n'est pas vide, on défile un nœud, on l'affiche, puis on enfile ses fils gauche et droit.

Entraîne-toi★★☆Pas commencé
Un arbre binaire a pour racine 10. Le fils gauche de 10 est 6, son fils droit est 14. Le fils gauche de 6 est 3 ; le fils droit de 6 est 8. Le fils gauche de 14 est 12 ;…

Un arbre binaire a pour racine 10. Le fils gauche de 10 est 6, son fils droit est 14. Le fils gauche de 6 est 3 ; le fils droit de 6 est 8. Le fils gauche de 14 est 12 ; 14 n'a pas de fils droit.

  1. a) Dessiner (décrire) l'arbre et donner sa taille et sa hauteur (on convient qu'un arbre réduit à sa seule racine a pour hauteur 1).
  1. b) Donner les parcours préfixe, infixe et suffixe.
  1. c) Donner le parcours en largeur.
  1. d) Montrer que cet arbre est un arbre binaire de recherche.
Résoudre

Arbres binaires de recherche

Un arbre binaire de recherche (ABR) range des valeurs comparables avec la règle suivante : pour chaque nœud de valeur $v$, toutes les valeurs du sous-arbre gauche sont inférieures ou égales à $v$ (ou strictement inférieures, selon la convention) et toutes celles du sous-arbre droit sont supérieures.

Arbre binaire de recherche de racine 8, de fils 3 et 10, et de feuilles 1 et 6. 8 3 10 1 6

Pourquoi c'est utile ? Pour chercher une valeur, on la compare à la racine : si elle est plus petite on va à gauche, sinon à droite. Chaque comparaison élimine un sous-arbre : dans un arbre bien équilibré, le coût est en $\log_2 n$ au lieu de $n$.

Parcours infixe. Il visite d'abord les valeurs inférieures à la racine, puis la racine, puis les valeurs supérieures, et ce à chaque niveau. Le parcours infixe d'un ABR donne donc les valeurs dans l'ordre croissant (ici : 1, 3, 6, 8, 10).

Insertion

Pour insérer v : si l'arbre est vide, on crée un nœud ; sinon on insère récursivement dans le sous-arbre gauche si v est plus petit que la racine, dans le droit sinon.

class Noeud:

    def __init__(self, valeur):

        self.valeur = valeur

        self.gauche = None

        self.droit = None

def inserer(arbre, v):

    if arbre is None:

        return Noeud(v)

    if v < arbre.valeur:

        arbre.gauche = inserer(arbre.gauche, v)

    else:

        arbre.droit = inserer(arbre.droit, v)

    return arbre
Entraîne-toi★★★Pas commencé
Un arbre binaire de hauteur $h$ (la racine seule ayant pour hauteur 1) a une taille $n$. a) Montrer que $h \le n \le 2^h - 1$. b) En déduire un encadrement de $h$ en…

Un arbre binaire de hauteur $h$ (la racine seule ayant pour hauteur 1) a une taille $n$.

  1. a) Montrer que $h \le n \le 2^h - 1$.
  1. b) En déduire un encadrement de $h$ en fonction de $n$ et justifier que $h \ge \lceil \log_2(n+1) \rceil$.
  1. c) Un arbre binaire de recherche de 1 000 nœuds a au minimum quelle hauteur ? Donner la hauteur maximale et expliquer dans quel cas elle est atteinte.
Résoudre
Entraîne-toi★★★Pas commencé
On représente un arbre binaire de recherche par une classe Noeud possédant les attributs valeur, gauche et droit (ces deux derniers valant None en l'absence de fils). a)…

On représente un arbre binaire de recherche par une classe Noeud possédant les attributs valeur, gauche et droit (ces deux derniers valant None en l'absence de fils).

  1. a) Écrire une fonction récursive inserer(arbre, v) qui insère la valeur v dans l'arbre et renvoie la racine.
  1. b) Dessiner l'arbre obtenu en insérant successivement 8, 3, 10, 1, 6, 14, 4, puis donner son parcours infixe.
  1. c) Justifier que le parcours infixe d'un arbre binaire de recherche fournit les valeurs triées dans l'ordre croissant.
  1. d) Donner la hauteur de l'arbre obtenu si l'on insère 1, 2, 3, 4, 5, 6, 7 dans cet ordre et commenter le coût d'une recherche.
Résoudre

Vers le début : dans la salle d'attente, la file garantit l'ordre d'arrivée ; pour « Retour », la pile ramène à la dernière page.

L'essentiel

  • Pile (LIFO) : empiler, depiler, est_vide ; on n'accède qu'au sommet.

  • File (FIFO) : enfiler, defiler, est_vide ; on ajoute à la queue, on retire à la tête. Plusieurs implémentations sont possibles (liste Python, deux piles).

  • Arbre binaire : racine, nœuds, feuilles, taille $n$, hauteur $h$, avec $h \le n \le 2^h - 1$.

  • Parcours : préfixe, infixe, suffixe (en profondeur) ; en largeur (avec une file).

  • ABR : gauche ≤ nœud < droit ; le parcours infixe est trié ; la recherche coûte environ $\log_2 n$ si l'arbre est équilibré, $n$ s'il est dégénéré.

Voir tous les exercices de cette partie