Retour au blog

Structures de Données Essentielles: Piles, Files, Listes

Plonge dans le monde des structures de données fondamentales avec cette série d'exercices conçue pour solidifier tes connaissances en programmation.

Cet article a été rédigé à des fins pédagogiques. Les informations présentées peuvent évoluer. Nous t’invitons à vérifier auprès de sources officielles.

Bienvenue à l'ORBITECH AI Academy !

Salut à toi, futur expert en informatique ! Aujourd'hui, on va s'attaquer à un pilier fondamental de la programmation : les structures de données. Listes, piles et files sont des outils indispensables pour organiser et manipuler l'information de manière efficace. Cette série d'exercices progressifs est conçue pour te permettre de maîtriser leurs principes, leurs opérations et leurs implémentations, te préparant ainsi à des défis algorithmiques plus complexes.

Compétences travaillées :

  • Comprendre et différencier les structures de données : listes chaînées, piles (LIFO) et files (FIFO).
  • Maîtriser les opérations de base (ajout, suppression, consultation) sur ces structures.
  • Appliquer ces structures pour résoudre des problèmes algorithmiques.
  • Implémenter des structures de données simples en pseudo-code ou avec des types abstraits.

Erreurs Fréquentes à Éviter :

  • Confondre le principe LIFO (Last-In, First-Out) des piles avec le principe FIFO (First-In, First-Out) des files.
  • Oublier de gérer les cas limites lors de l'implémentation (structure vide, structure pleine, etc.).
  • Ne pas actualiser correctement les pointeurs ou références dans les listes chaînées lors des insertions/suppressions.
  • Tenter d'accéder à un élément inexistant sans vérification préalable.

Exercices de Pratique

Exercice 1 : Concepts Fondamentaux

Définis avec tes propres mots les structures de données suivantes et donne un exemple d'application concrète pour chacune :

  1. Une Pile
  2. Une File
  3. Une Liste Chaînée (simple)

Barème indicatif : 2 points

Correction :

  1. Une Pile :

    Une pile est une structure de données linéaire qui suit le principe LIFO (Last-In, First-Out). Le dernier élément ajouté est le premier à être retiré. Imagine une pile d'assiettes : tu ajoutes des assiettes sur le dessus, et tu retires toujours celle du dessus.

    Exemple d'application : La gestion des appels de fonctions dans un programme (pile d'exécution), l'historique "annuler/rétablir" d'un éditeur de texte, la vérification de parenthèses équilibrées.

  2. Une File :

    Une file est une structure de données linéaire qui suit le principe FIFO (First-In, First-Out). Le premier élément ajouté est le premier à être retiré. Imagine une file d'attente à la caisse : le premier client arrivé est le premier servi.

    Exemple d'application : La gestion des tâches dans un système d'exploitation, l'impression de documents (spool d'impression), les requêtes réseau en attente, les algorithmes de parcours en largeur (BFS).

  3. Une Liste Chaînée (simple) :

    Une liste chaînée est une collection ordonnée d'éléments (appelés nœuds), où chaque nœud contient une donnée et une référence (ou pointeur) vers le nœud suivant. Contrairement aux tableaux, les éléments ne sont pas stockés de manière contiguë en mémoire. On peut parcourir la liste en suivant les références d'un nœud à l'autre.

    Exemple d'application : L'implémentation de piles et de files, la gestion de listes de lecture musicales, la représentation de polynômes, les systèmes de gestion de mémoire.

Résultat : Définitions claires avec exemples pertinents.

Point méthode : Comprendre la différence entre LIFO et FIFO est crucial. LIFO pour "pile" (stack), FIFO pour "file" (queue).

Exercice 2 : Opérations sur une Pile

Soit une pile initialement vide. Représente l'état de la pile après chaque opération suivante :

  1. Empiler (push) 'A'
  2. Empiler (push) 'B'
  3. Dépiler (pop)
  4. Empiler (push) 'C'
  5. Empiler (push) 'D'
  6. Dépiler (pop)

Barème indicatif : 1.5 points

Correction :

On représente la pile verticalement, le sommet étant le dernier élément.

  1. Empiler 'A' :
    [A]
    
  2. Empiler 'B' :
    [B]
    [A]
    
  3. Dépiler : 'B' est retiré.
    [A]
    
  4. Empiler 'C' :
    [C]
    [A]
    
  5. Empiler 'D' :
    [D]
    [C]
    [A]
    
  6. Dépiler : 'D' est retiré.
    [C]
    [A]
    

Résultat final de la pile : [C, A] (avec 'C' au sommet).

Astuce : Visualise toujours le sommet de la pile pour savoir quel élément sera dépilé. Le principe LIFO est la clé.

Exercice 3 : Opérations sur une File

Soit une file initialement vide. Représente l'état de la file (éléments dans l'ordre de leur position) après chaque opération suivante :

  1. Enfiler (enqueue) 'X'
  2. Enfiler (enqueue) 'Y'
  3. Défiler (dequeue)
  4. Enfiler (enqueue) 'Z'
  5. Enfiler (enqueue) 'W'
  6. Défiler (dequeue)

Barème indicatif : 1.5 points

Correction :

On représente la file horizontalement, le début (tête) à gauche et la fin (queue) à droite.

  1. Enfiler 'X' :
    [X]
    
  2. Enfiler 'Y' :
    [X, Y]
    
  3. Défiler : 'X' est retiré (le premier arrivé).
    [Y]
    
  4. Enfiler 'Z' :
    [Y, Z]
    
  5. Enfiler 'W' :
    [Y, Z, W]
    
  6. Défiler : 'Y' est retiré (le premier arrivé parmi les restants).
    [Z, W]
    

Résultat final de la file : [Z, W] (avec 'Z' en tête).

Astuce : Pense à la file d'attente d'une caisse. Le premier qui arrive est le premier à partir. Le principe FIFO est essentiel ici.

Exercice 4 : Parcours de Liste Chaînée

On considère une liste chaînée simple représentée par une séquence de nœuds. Chaque nœud contient une valeur et un pointeur vers le nœud suivant. Le dernier nœud pointe vers NULL.

Liste : 10 -> 20 -> 30 -> 40 -> NULL

Décris en pseudo-code l'algorithme pour parcourir cette liste et afficher toutes les valeurs qu'elle contient.

Barème indicatif : 1.5 points

Correction :

Pour parcourir une liste chaînée, tu dois commencer par le premier nœud (la tête de la liste) et suivre les pointeurs de nœud en nœud jusqu'à atteindre NULL.

PROCEDURE AfficherListe(tete_liste)
 nœud_actuel = tete_liste
 TANT QUE nœud_actuel EST NON_NULL FAIRE
 AFFICHER nœud_actuel.valeur
 nœud_actuel = nœud_actuel.suivant
 FIN TANT QUE
FIN PROCEDURE

Résultat : L'algorithme affichera : 10, 20, 30, 40.

Point méthode : Le parcours de liste chaînée est un concept fondamental. Il est important de ne pas oublier d'avancer le pointeur nœud_actuel pour éviter une boucle infinie.

Exercice 5 : Implémentation de Pile avec un Tableau Dynamique (ou Liste Python)

Propose un pseudo-code pour implémenter une Pile en utilisant un tableau dynamique (comme une liste en Python). Tu devras définir les fonctions empiler(element), depiler() et est_vide().

Barème indicatif : 2 points

Correction :

Une pile peut être facilement implémentée à l'aide d'un tableau dynamique où l'on ajoute et retire toujours à la fin.

CLASSE Pile:
 ATTRIBUT tableau_interne : liste vide

 PROCEDURE empiler(element):
 AJOUTER element À LA FIN de tableau_interne
 FIN PROCEDURE

 PROCEDURE depiler():
 SI est_vide() ALORS
 AFFICHER "Erreur: Pile vide"
 RETOURNE NULL // Ou lever une exception
 SINON
 RETOURNE ET SUPPRIMER LE DERNIER élément de tableau_interne
 FIN SI
 FIN PROCEDURE

 FONCTION est_vide():
 RETOURNE VRAI SI tableau_interne EST VIDE, FAUX SINON
 FIN FONCTION

 FONCTION sommet():
 SI est_vide() ALORS
 RETOURNE NULL
 SINON
 RETOURNE LE DERNIER élément de tableau_interne
 FIN SI
 FIN FONCTION
FIN CLASSE

Résultat : Pseudo-code fonctionnel pour une pile basée sur un tableau dynamique.

Astuce : En Python, tu utiliserais append() pour empiler, pop() pour dépiler, et len() == 0 pour vérifier si la pile est vide. C'est l'un des moyens les plus simples d'implémenter une pile.

Exercice 6 : Implémentation de File avec un Tableau Dynamique

Propose un pseudo-code pour implémenter une File en utilisant un tableau dynamique. Tu devras définir les fonctions enfiler(element), defiler() et est_vide().

Barème indicatif : 2 points

Correction :

L'implémentation d'une file avec un tableau dynamique est un peu plus délicate qu'une pile. Si tu utilises pop() au début du tableau pour defiler(), cela peut être coûteux en performance car tous les éléments doivent être décalés. Cependant, pour cet exercice en pseudo-code, on acceptera cette approche simple.

CLASSE File:
 ATTRIBUT tableau_interne : liste vide

 PROCEDURE enfiler(element):
 AJOUTER element À LA FIN de tableau_interne
 FIN PROCEDURE

 PROCEDURE defiler():
 SI est_vide() ALORS
 AFFICHER "Erreur: File vide"
 RETOURNE NULL
 SINON
 RETOURNE ET SUPPRIMER LE PREMIER élément de tableau_interne
 FIN SI
 FIN PROCEDURE

 FONCTION est_vide():
 RETOURNE VRAI SI tableau_interne EST VIDE, FAUX SINON
 FIN FONCTION

 FONCTION premier():
 SI est_vide() ALORS
 RETOURNE NULL
 SINON
 RETOURNE LE PREMIER élément de tableau_interne
 FIN SI
 FIN FONCTION
FIN CLASSE

Résultat : Pseudo-code fonctionnel pour une file basée sur un tableau dynamique.

Point méthode : Bien que fonctionnelle, l'opération supprimer le premier élément d'un tableau dynamique peut avoir une complexité de $O(N)$ car elle nécessite de décaler tous les autres éléments. Pour une file plus performante, on utiliserait souvent une liste chaînée ou un tableau circulaire.

Exercice 7 : Vérification de Parenthèses Équilibrées

Écris un pseudo-code pour vérifier si une expression mathématique contient des parenthèses, crochets et accolades équilibrés. Par exemple :

  • "([{}])" est équilibré.
  • "({[)})" n'est pas équilibré.
  • "[(])" n'est pas équilibré.

Utilise une structure de pile pour résoudre ce problème.

Barème indicatif : 3 points

Correction :

Ce problème est un cas d'école pour l'utilisation des piles. L'idée est d'empiler les parenthèses ouvrantes et de dépiler quand une parenthèse fermante correspond. Si la pile est vide à la fin et qu'aucune incohérence n'a été trouvée, l'expression est équilibrée.

FONCTION SontParenthèsesÉquilibrées(expression):
 pile = CREER_PILE_VIDE()
 parenthèses_ouvrantes = "([{"
 parenthèses_fermantes = ")]}"
 paires_parenthèses = {')': '(', ']': '[', '}': '{'}

 POUR CHAQUE caractère DANS expression FAIRE
 SI caractère EST DANS parenthèses_ouvrantes ALORS
 empiler(pile, caractère)
 SINON SI caractère EST DANS parenthèses_fermantes ALORS
 SI est_vide(pile) ALORS
 RETOURNE FAUX // Une parenthèse fermante sans parenthèse ouvrante correspondante
 FIN SI
 dernier_ouvrant = depiler(pile)
 SI dernier_ouvrant N'EST PAS paires_parenthèses[caractère] ALORS
 RETOURNE FAUX // Non-correspondance
 FIN SI
 FIN SI
 FIN POUR

 RETOURNE est_vide(pile) // Vrai si toutes les parenthèses ont été appariées
FIN FONCTION

Résultat : Pseudo-code pour vérifier l'équilibre des parenthèses à l'aide d'une pile.

Point méthode : L'utilisation d'un dictionnaire (ou map) pour stocker les paires de parenthèses est une approche élégante et efficace.

Exercice 8 : Utilisation de File pour le Parcours en Largeur (BFS - Conceptuel)

Imagine que tu as un réseau social simple où chaque personne est un nœud et les amitiés sont des connexions. Tu veux trouver tous les amis d'un ami, puis les amis de ces amis, et ainsi de suite, en partant d'une personne donnée (le point de départ). Explique comment une file peut t'aider à explorer ce réseau par niveaux, garantissant que tu vois tous les amis proches avant de passer aux amis plus éloignés.

Barème indicatif : 2 points

Correction :

L'exploration par niveaux est exactement ce que le Parcours en Largeur (BFS - Breadth-First Search) fait, et une file est l'outil parfait pour cela.

Voici l'explication conceptuelle :

  1. Initialisation : Tu commences avec la personne de départ. Tu l'ajoutes à une file (c'est la première personne à "visiter") et tu la marques comme "visitée" pour ne pas la traiter plusieurs fois.
  2. Boucle d'exploration : Tant que la file n'est pas vide :
    • Tu défiles une personne de la file (c'est la personne actuelle que tu explores).
    • Tu regardes tous les amis de cette personne (ses voisins dans le réseau).
    • Pour chaque ami :
      • Si cet ami n'a pas encore été visité, tu l'ajoutes à la file (pour le visiter plus tard) et tu le marques comme "visitée".
  3. Résultat : En utilisant une file, tu t'assures que toutes les personnes à distance 1 (amis directs) sont ajoutées à la file et traitées avant que tu ne commences à traiter les personnes à distance 2 (amis d'amis), et ainsi de suite. Cela garantit un parcours "par couches" ou "par niveaux".

Résultat : Explication claire de l'application d'une file pour le BFS.

Astuce : La file maintient l'ordre de découverte, ce qui est essentiel pour un parcours en largeur. Sans elle, tu pourrais te perdre dans une branche du réseau avant d'explorer les autres.

Exercice 9 : Inverser une Pile SANS Utiliser de Structure Auxiliaire (Sauf Récursion)

C'est un défi classique ! Écris un pseudo-code pour inverser une pile. Tu n'as pas le droit d'utiliser une autre pile, une file, ou un tableau/liste temporaire. La seule "structure" autorisée en plus de la pile elle-même est la pile d'appels de fonction (donc tu peux utiliser la récursion).

Tu devras créer deux fonctions récursives :

  1. insérer_au_fond(pile, element) : Qui insère un élément au fond de la pile.
  2. inverser_pile(pile) : Qui utilise la première fonction pour inverser la pile.

Barème indicatif : 3 points

Correction :

Cet exercice est complexe car il faut penser de manière récursive à la fois pour insérer un élément au fond et pour inverser la pile.

FONCTION est_vide(pile): // Supposons cette fonction disponible
 // .
FONCTION depiler(pile): // Supposons cette fonction disponible
 // .
PROCEDURE empiler(pile, element): // Supposons cette fonction disponible
 // .

PROCEDURE insérer_au_fond(pile, element_a_inserer):
 SI est_vide(pile) ALORS
 empiler(pile, element_a_inserer)
 SINON
 temp = depiler(pile)
 insérer_au_fond(pile, element_a_inserer)
 empiler(pile, temp)
 FIN SI
FIN PROCEDURE

PROCEDURE inverser_pile(pile):
 SI NON est_vide(pile) ALORS
 element_du_sommet = depiler(pile)
 inverser_pile(pile) // Inverser le reste de la pile
 insérer_au_fond(pile, element_du_sommet) // Mettre l'ancien sommet au fond
 FIN SI
FIN PROCEDURE

Résultat : Pseudo-code récursif pour inverser une pile sans structure auxiliaire explicite.

Astuce : C'est un excellent exemple de la puissance de la récursion pour simuler des structures de données. La pile d'appels de fonctions agit comme une pile auxiliaire implicite.

Exercice 10 : Implémentation d'une Liste Doublement Chaînée

Décris en pseudo-code l'implémentation d'une liste doublement chaînée. Pour simplifier, tu te concentreras sur la structure des nœuds et la fonction d'insertion d'un élément à une position donnée (par exemple, après un nœud spécifique).

Un nœud doit contenir : une valeur, un pointeur vers le nœud précédent, et un pointeur vers le nœud suivant.

Barème indicatif : 3 points

Correction :

La liste doublement chaînée ajoute un pointeur "précédent" à chaque nœud, ce qui permet un parcours dans les deux sens et simplifie certaines opérations comme la suppression.

CLASSE Nœud:
 ATTRIBUT valeur
 ATTRIBUT précédent : Nœud (pointeur vers le Nœud précédent)
 ATTRIBUT suivant : Nœud (pointeur vers le Nœud suivant)

 CONSTRUCTEUR Nœud(valeur):
 CECI.valeur = valeur
 CECI.précédent = NULL
 CECI.suivant = NULL
 FIN CONSTRUCTEUR
FIN CLASSE

CLASSE ListeDoublementChaînée:
 ATTRIBUT tête : Nœud (premier Nœud de la liste)
 ATTRIBUT queue : Nœud (dernier Nœud de la liste)

 CONSTRUCTEUR ListeDoublementChaînée():
 CECI.tête = NULL
 CECI.queue = NULL
 FIN CONSTRUCTEUR

 PROCEDURE insérer_après(nœud_existant, nouvelle_valeur):
 SI nœud_existant EST NULL ALORS
 AFFICHER "Erreur: Le nœud existant ne peut pas être NULL."
 RETOUR
 FIN SI

 nouveau_nœud = Nœud(nouvelle_valeur)

 nouveau_nœud.suivant = nœud_existant.suivant
 nouveau_nœud.précédent = nœud_existant

 SI nœud_existant.suivant EST NON_NULL ALORS
 nœud_existant.suivant.précédent = nouveau_nœud
 SINON // Si nœud_existant était la queue
 CECI.queue = nouveau_nœud
 FIN SI

 nœud_existant.suivant = nouveau_nœud

 FIN PROCEDURE

 // Pour l'exemple, ajoutons une fonction d'ajout en fin pour initialisation
 PROCEDURE ajouter_en_fin(valeur):
 nouveau_nœud = Nœud(valeur)
 SI CECI.tête EST NULL ALORS
 CECI.tête = nouveau_nœud
 CECI.queue = nouveau_nœud
 SINON
 CECI.queue.suivant = nouveau_nœud
 nouveau_nœud.précédent = CECI.queue
 CECI.queue = nouveau_nœud
 FIN SI
 FIN PROCEDURE
FIN CLASSE

Résultat : Pseudo-code pour les structures de Nœud et Liste Doublement Chaînée avec une fonction d'insertion après un nœud.

Point méthode : L'ajout ou la suppression dans une liste doublement chaînée implique de manipuler deux pointeurs (précédent et suivant) pour chaque nœud impacté, ce qui peut être un peu plus délicat que pour une liste simple. Attention aux cas limites : insertion en début, en fin, et sur une liste vide.

Comment ORBITECH Peut T'aider

ORBITECH AI Academy met à ta disposition des outils concrets pour réviser plus efficacement et progresser à ton rythme.

Tous ces outils sont disponibles sur ta plateforme ORBITECH. Connecte-toi et explore ceux qui correspondent le mieux à tes besoins !

Commencer gratuitement

Contenu en libre diffusion — partage autorisé sous réserve de mentionner ORBITECH AI Academy comme source.

COMMENCE DÈS MAINTENANT

Rejoins des milliers d’étudiants qui utilisent ORBITECH pour exceller.

Commencer gratuitement
🌍 ORBITECH AI Academy — Free education in 88 languages for 171 countries