{
  "chapter": {
    "id": "listes-piles-files",
    "level": "terminale",
    "theme": "Structures de données",
    "title": "Listes, piles, files",
    "description": "Structures de données linéaires : listes chaînées, piles\n(LIFO) et files (FIFO). Opérations de base, complexité,\nimplémentations possibles, applications classiques (parcours,\nparenthésage, parcours en largeur, etc.).",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "pile",
        "definition"
      ],
      "title": "Définition d'une pile",
      "statement": "Quel est le principe d'une **pile** (*stack*) ?",
      "options": [
        {
          "text": "Premier entré, premier sorti (FIFO)",
          "correct": false,
          "feedback": "Erreur : c'est le principe d'une **file** (queue),\npas d'une pile. Confusion classique entre les deux.\n"
        },
        {
          "text": "Accès aléatoire à n'importe quel élément",
          "correct": false,
          "feedback": "Erreur : ce serait un tableau classique. Une pile\nimpose un accès uniquement par le sommet.\n"
        },
        {
          "text": "Tri automatique des éléments",
          "correct": false,
          "feedback": "Erreur : aucun tri. Les éléments sont stockés dans\nleur ordre d'arrivée, accessible uniquement par\nle sommet.\n"
        },
        {
          "text": "Dernier entré, premier sorti (LIFO)",
          "correct": true,
          "feedback": "Bonne réponse : LIFO (*Last In, First Out*). On\najoute (push) et on retire (pop) toujours par le\n**sommet**. C'est le comportement d'une pile\nd'assiettes : on prend toujours celle du haut.\n"
        }
      ],
      "explanation": "Les piles sont fondamentales en informatique : pile\nd'appels de fonctions, gestion d'historique (Ctrl+Z),\nanalyse syntaxique de parenthèses, parcours en\nprofondeur d'un graphe."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "file",
        "definition"
      ],
      "title": "Définition d'une file",
      "statement": "Quel est le principe d'une **file** (*queue*) ?",
      "options": [
        {
          "text": "Élimination automatique des doublons",
          "correct": false,
          "feedback": "Erreur : aucune dédoublonnage automatique.\n"
        },
        {
          "text": "Premier entré, premier sorti (FIFO)",
          "correct": true,
          "feedback": "Bonne réponse : FIFO (*First In, First Out*). On\najoute en queue (enfilement) et on retire en tête\n(défilement). C'est le comportement d'une file\nd'attente au supermarché.\n"
        },
        {
          "text": "Dernier entré, premier sorti (LIFO)",
          "correct": false,
          "feedback": "Erreur : c'est le principe d'une **pile**, pas\nd'une file.\n"
        },
        {
          "text": "Accès direct au milieu de la structure",
          "correct": false,
          "feedback": "Erreur : la file impose l'accès uniquement aux\nextrémités.\n"
        }
      ],
      "explanation": "Les files sont utilisées pour : tâches en attente\n(imprimante, ordonnanceur), parcours en largeur d'un graphe,\nbuffers de communication, et tout système où l'on\nveut respecter l'ordre d'arrivée."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "operations"
      ],
      "title": "Opérations sur une pile",
      "statement": "Quelles sont les **deux opérations principales** d'une\npile ?",
      "options": [
        {
          "text": "`get` et `set`",
          "correct": false,
          "feedback": "Erreur : ce sont des opérations d'accès indexé,\nplutôt sur un tableau ou un dictionnaire.\n"
        },
        {
          "text": "`add` et `remove`",
          "correct": false,
          "feedback": "Erreur : ces noms sont génériques. Les noms\nstandard sur une pile sont plus spécifiques.\n"
        },
        {
          "text": "`push` (empiler) et `pop` (dépiler)",
          "correct": true,
          "feedback": "Bonne réponse : `push(x)` ajoute `x` au sommet ;\n`pop()` retire et renvoie l'élément du sommet. On\na aussi souvent `peek()` ou `top()` (consulter\nsans retirer) et `is_empty()` (tester la vacuité).\n"
        },
        {
          "text": "`enqueue` et `dequeue`",
          "correct": false,
          "feedback": "Erreur : ce sont les opérations d'une **file**,\npas d'une pile.\n"
        }
      ],
      "explanation": "Toutes ces opérations s'effectuent en temps **constant**\n$O(1)$ avec une bonne implémentation. C'est l'avantage\nprincipal des piles et files par rapport aux\nstructures plus complexes."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "operations-file"
      ],
      "title": "Opérations sur une file",
      "statement": "Quelles sont les opérations principales d'une **file** ?",
      "options": [
        {
          "text": "`push` et `pop`",
          "correct": false,
          "feedback": "Erreur : ce sont les opérations d'une **pile**,\npas d'une file.\n"
        },
        {
          "text": "`min` et `max`",
          "correct": false,
          "feedback": "Erreur : ce sont des opérations d'agrégation, pas\nde file.\n"
        },
        {
          "text": "`enqueue` (enfiler) et `dequeue` (défiler)",
          "correct": true,
          "feedback": "Bonne réponse : `enqueue(x)` ajoute `x` à la queue ;\n`dequeue()` retire et renvoie l'élément en tête.\nComme pour la pile, ces opérations sont en $O(1)$.\n"
        },
        {
          "text": "`sort` et `reverse`",
          "correct": false,
          "feedback": "Erreur : opérations sur les listes, pas sur les\nfiles.\n"
        }
      ],
      "explanation": "Les noms varient selon les langages : `push` /\n`shift` en JavaScript, `append` / `popleft` avec\n`collections.deque` en Python, etc. Le concept reste\nle même."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "exemple-application"
      ],
      "title": "Application classique des piles",
      "statement": "Pour vérifier qu'une expression contient des\n**parenthèses bien équilibrées** (par exemple\n`((a + b) * c)`), quelle structure est la plus adaptée ?",
      "options": [
        {
          "text": "Une pile (LIFO)",
          "correct": true,
          "feedback": "Bonne réponse : on empile les parenthèses\nouvrantes ; à chaque parenthèse fermante, on\ndépile et on vérifie la correspondance. À la fin,\nla pile doit être vide. C'est l'algorithme\nstandard.\n"
        },
        {
          "text": "Un tableau trié",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec un tri. C'est l'ordre\nd'apparition qui compte, ce qui est une pile.\n"
        },
        {
          "text": "Une file (FIFO)",
          "correct": false,
          "feedback": "Erreur : la file ne convient pas. Pour\nl'équilibrage, on a besoin d'accéder à la\nparenthèse ouvrante la plus récente, ce qui est\ndu LIFO.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : le dictionnaire n'est pas adapté à un\nordre temporel. La pile est la bonne structure.\n"
        }
      ],
      "explanation": "Cet exemple illustre bien le LIFO : la dernière\nparenthèse ouverte est celle qui doit être fermée en\npremier. L'algorithme s'étend aux crochets `[]`,\naccolades `{}`, balises HTML, etc."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "exemple-file"
      ],
      "title": "Application classique des files",
      "statement": "Pour gérer une **file d'impression** (les documents\narrivés en premier sont imprimés en premier), quelle\nstructure utiliser ?",
      "options": [
        {
          "text": "Une pile",
          "correct": false,
          "feedback": "Erreur : avec une pile, le dernier document arrivé\nserait imprimé en premier, ce qui est injuste.\n"
        },
        {
          "text": "Une liste triée par taille",
          "correct": false,
          "feedback": "Erreur : trier par taille fausserait l'ordre\nd'arrivée. Sauf si on veut une politique\nspécifique (le plus petit d'abord), mais ce n'est\npas FIFO.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : pas de notion d'ordre temporel naturelle\ndans un dictionnaire.\n"
        },
        {
          "text": "Une file",
          "correct": true,
          "feedback": "Bonne réponse : la file (FIFO) garantit que\nl'ordre d'arrivée est respecté. C'est exactement\nl'usage typique : ordonnanceur du système\nd'exploitation, file d'impression, tampon\nréseau.\n"
        }
      ],
      "explanation": "Variantes possibles : files de **priorité** (où\nl'ordre dépend d'une priorité, pas seulement\nd'arrivée), implémentées par des tas (heaps) plutôt\nque des files simples."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "pile-python"
      ],
      "title": "Pile en Python",
      "statement": "En Python, une manière simple d'implémenter une pile\nutilise :",
      "options": [
        {
          "text": "Une liste avec `append()` pour empiler et `pop()` pour dépiler",
          "correct": true,
          "feedback": "Bonne réponse : `lst.append(x)` ajoute en fin\n(sommet) et `lst.pop()` retire la fin (sommet),\ntous deux en $O(1)$ amorti. Simple et efficace.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : le dictionnaire n'a pas de notion d'ordre\nen place pour cet usage.\n"
        },
        {
          "text": "Un simple entier",
          "correct": false,
          "feedback": "Un entier est une valeur\nunique : il ne peut pas\ncontenir une suite\nd'éléments empilés\nsuccessivement.\n"
        },
        {
          "text": "Une chaîne de caractères",
          "correct": false,
          "feedback": "Erreur : les chaînes sont immuables en Python.\nEmpiler créerait une nouvelle chaîne à chaque\nopération, c'est inefficace.\n"
        }
      ],
      "explanation": "Pour une pile « propre », on préférera implémenter une\nclasse `Pile` avec méthodes `empiler`, `depiler`,\n`est_vide`, etc. Cela cache la liste sous-jacente et\nempêche les usages aberrants (accès indexé direct)."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "file-python"
      ],
      "title": "File en Python efficace",
      "statement": "Pour implémenter une file efficace en Python, quelle\nstructure de la bibliothèque standard utilise-t-on ?",
      "options": [
        {
          "text": "`collections.deque` (file double-ended) avec `append()` et `popleft()`, toutes deux en $O(1)$",
          "correct": true,
          "feedback": "Bonne réponse : `deque` est la structure adaptée.\nElle permet l'ajout et la suppression aux deux\nbouts en temps constant.\n"
        },
        {
          "text": "`tuple`",
          "correct": false,
          "feedback": "Erreur : un tuple est immuable. On ne peut pas y\najouter d'éléments.\n"
        },
        {
          "text": "`list`, en utilisant `append()` et `pop(0)`",
          "correct": false,
          "feedback": "Erreur : `pop(0)` est en $O(n)$ car il faut\ndécaler tous les éléments. C'est inefficace pour\nde grandes files.\n"
        },
        {
          "text": "`set` (ensemble)",
          "correct": false,
          "feedback": "Erreur : un ensemble n'a pas d'ordre. Inadapté à\nune file.\n"
        }
      ],
      "explanation": "Bonne pratique : `from collections import deque ;\nf = deque() ; f.append(x) ; f.popleft()`. C'est aussi\nce qu'utilisent les algorithmes parcours en largeur sur des graphes."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "pile-appels"
      ],
      "title": "Pile d'appels",
      "statement": "Quel rôle joue la **pile d'appels** (*call stack*) lors\nde l'exécution d'un programme ?",
      "options": [
        {
          "text": "Stocker les fichiers ouverts",
          "correct": false,
          "feedback": "Erreur : les fichiers sont gérés par le système\nd'exploitation, pas par la pile d'appels.\n"
        },
        {
          "text": "Stocker les données d'une base de données",
          "correct": false,
          "feedback": "Une base de données est\nstockée sur le disque\ndans des structures\ndédiées, sans rapport\navec la pile d'appels\ngérée par\nl'interpréteur.\n"
        },
        {
          "text": "Compter les variables globales",
          "correct": false,
          "feedback": "Erreur : aucun rapport. La pile d'appels gère les\ncontextes locaux.\n"
        },
        {
          "text": "Empiler le contexte de chaque appel de fonction (paramètres, variables locales, adresse de retour) et le dépiler à la fin de la fonction",
          "correct": true,
          "feedback": "Bonne réponse : c'est exactement le rôle d'une\npile au sens LIFO. Quand une fonction A appelle B,\nle contexte de A reste empilé sous celui de B,\njusqu'à ce que B se termine et qu'on dépile pour\nrevenir à A.\n"
        }
      ],
      "explanation": "C'est précisément la pile d'appels qui sature en cas\nde récursion infinie, provoquant le célèbre\n`RecursionError` ou *stack overflow*. Sa taille est\ntypiquement limitée à quelques dizaines de milliers\nd'appels."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "liste-chainee"
      ],
      "title": "Liste chaînée",
      "statement": "Qu'est-ce qu'une **liste chaînée** ?",
      "options": [
        {
          "text": "Une liste reliée à une base de données",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec une base de données.\n"
        },
        {
          "text": "Une liste qui ne peut pas être modifiée",
          "correct": false,
          "feedback": "Erreur : ce serait une liste immuable. Une liste\nchaînée peut tout à fait être modifiée.\n"
        },
        {
          "text": "Une liste chiffrée",
          "correct": false,
          "feedback": "Erreur : « chaînée » et « chiffrée » sont des\nmots différents. Aucun rapport avec la\ncryptographie.\n"
        },
        {
          "text": "Une liste où chaque élément contient sa valeur et une référence vers l'élément suivant",
          "correct": true,
          "feedback": "Bonne réponse : contrairement à un tableau (mémoire\ncontiguë), une liste chaînée est composée de\nmaillons qui se référencent. Ajouter ou supprimer\nen tête se fait en $O(1)$, mais l'accès indexé\ncoûte $O(n)$.\n"
        }
      ],
      "explanation": "Variantes : listes **simplement chaînées** (un seul\npointeur next), **doublement chaînées** (next et\nprevious), **circulaires** (le dernier pointe vers le\npremier). Chaque variante a ses cas d'usage."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "complexite-pile"
      ],
      "title": "Complexité des opérations sur une pile",
      "statement": "Quelle est la complexité des opérations `push` et `pop`\nsur une pile, pour une bonne implémentation ?",
      "options": [
        {
          "text": "$O(n)$ pour les deux",
          "correct": false,
          "feedback": "Erreur : c'est trop coûteux. Les piles sont\nréputées efficaces parce que ces opérations sont\nen temps constant.\n"
        },
        {
          "text": "$O(\\log n)$ pour les deux",
          "correct": false,
          "feedback": "Erreur : pas de raison logarithmique. C'est un\naccès direct.\n"
        },
        {
          "text": "$O(1)$ pour les deux",
          "correct": true,
          "feedback": "Bonne réponse : `push` et `pop` agissent toujours\nau sommet, donc en temps constant. C'est ce qui\nrend les piles si utiles dans les algorithmes\ncomme la conversion infixe → postfixe ou le parcours en profondeur.\n"
        },
        {
          "text": "$O(n^2)$ pour les deux",
          "correct": false,
          "feedback": "Erreur : excessif.\n"
        }
      ],
      "explanation": "Avec une liste Python, `append` et `pop()` (sans\nargument) sont en $O(1)$ amorti. Avec une liste\nchaînée et un pointeur sur le sommet, c'est aussi\n$O(1)$. Pas de surprise."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "complexite-acces"
      ],
      "title": "Accès indexé dans une liste chaînée",
      "statement": "Quelle est la complexité d'accéder au $k$-ième élément\nd'une liste chaînée simplement chaînée ?",
      "options": [
        {
          "text": "$O(k)$ (linéaire)",
          "correct": true,
          "feedback": "Bonne réponse : il faut suivre les pointeurs un\npar un depuis la tête. Pour atteindre l'élément\n$k$, il faut $k$ étapes.\n"
        },
        {
          "text": "$O(\\log k)$",
          "correct": false,
          "feedback": "Erreur : pas de division par deux possible. C'est\nplutôt linéaire.\n"
        },
        {
          "text": "$O(1)$",
          "correct": false,
          "feedback": "Erreur : les listes chaînées **n'offrent pas**\nd'accès indexé en temps constant, contrairement\naux tableaux. Il faut parcourir.\n"
        },
        {
          "text": "$O(k^2)$",
          "correct": false,
          "feedback": "Erreur : excessif.\n"
        }
      ],
      "explanation": "C'est le compromis fondamental : une liste chaînée\noffre des insertions/suppressions efficaces ($O(1)$\nen tête, $O(n)$ ailleurs), mais un accès indexé lent.\nPour un accès rapide, utiliser un tableau."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "pile-evaluation"
      ],
      "title": "Évaluation d'une expression postfixée",
      "statement": "L'évaluation d'une expression en **notation postfixée**\n(par exemple `2 3 + 4 *`) utilise :",
      "options": [
        {
          "text": "Une file",
          "correct": false,
          "feedback": "Erreur : la file ne fonctionne pas pour ce cas. La\nnotation postfixée demande d'accéder aux deux\nderniers opérandes, ce qui est typiquement LIFO.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Le dictionnaire associe\ndes clés à des valeurs ;\nil ne reflète pas l'ordre\ntemporel d'arrivée des\nopérandes nécessaire à\nl'évaluation d'une\nexpression postfixée.\n"
        },
        {
          "text": "Un arbre binaire",
          "correct": false,
          "feedback": "Erreur : on peut **représenter** une expression par\nun arbre, mais l'évaluation postfixée utilise une\npile.\n"
        },
        {
          "text": "Une pile : on empile les nombres et, à chaque opérateur, on dépile les deux derniers, on calcule, et on rempile le résultat",
          "correct": true,
          "feedback": "Bonne réponse : pour `2 3 + 4 *`, on empile $2$,\npuis $3$ ; on rencontre `+`, on dépile $3$ et $2$,\non calcule $5$, on empile $5$ ; on empile $4$ ;\non rencontre `*`, on dépile $4$ et $5$, on calcule\n$20$. Résultat : $20$.\n"
        }
      ],
      "explanation": "Cette technique est utilisée dans les calculatrices\nRPN (HP, par exemple) et dans certaines machines\nvirtuelles (la JVM). Elle évite la gestion explicite\ndes parenthèses."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "bfs-dfs"
      ],
      "title": "Parcours en largeur et parcours en profondeur",
      "statement": "Pour un parcours en **largeur** d'un graphe ou\nd'un arbre, quelle structure utilise-t-on ?",
      "options": [
        {
          "text": "Une file",
          "correct": true,
          "feedback": "Bonne réponse : on enfile les voisins à explorer\n; en défilant à chaque étape, on visite d'abord\ntous les voisins du niveau actuel avant de passer\nau suivant. C'est exactement le parcours en\nlargeur.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : pas de structure d'ordre dans un\ndictionnaire (sauf en Python $3.7$+, mais ce\nn'est pas l'intention ici).\n"
        },
        {
          "text": "Une liste triée",
          "correct": false,
          "feedback": "Erreur : aucun tri n'est requis pour parcours en largeur.\n"
        },
        {
          "text": "Une pile",
          "correct": false,
          "feedback": "Erreur : la pile correspond au parcours en\n**profondeur**. Pour la largeur, c'est\ndifférent.\n"
        }
      ],
      "explanation": "Mémo : **pile = profondeur** ; **file = largeur**.\nC'est l'application algorithmique la plus\nclassique de ces deux structures."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "interface"
      ],
      "title": "Distinction entre interface et implémentation",
      "statement": "« Pile » et « file » sont des **interfaces** abstraites.\nCela signifie que :",
      "options": [
        {
          "text": "Elles sont écrites en assembleur",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec le langage.\n"
        },
        {
          "text": "Elles définissent un comportement (LIFO ou FIFO) sans imposer la structure interne (tableau, liste chaînée, etc.)",
          "correct": true,
          "feedback": "Bonne réponse : on peut implémenter une pile avec\nune liste Python, une liste chaînée, un tableau,\netc. Tant que les opérations respectent LIFO,\nc'est une pile. Cette abstraction est essentielle\nen programmation modulaire.\n"
        },
        {
          "text": "Elles sont obsolètes",
          "correct": false,
          "feedback": "Erreur : elles sont au contraire fondamentales et\nomniprésentes.\n"
        },
        {
          "text": "Elles ne peuvent pas être implémentées en Python",
          "correct": false,
          "feedback": "Erreur : Python permet bien sûr d'implémenter ces\nstructures.\n"
        }
      ],
      "explanation": "C'est l'idée des **types abstraits de données**\n(TAD) : on spécifie ce que la structure **doit\nfaire**, pas comment. Une pile peut être implémentée\nde plusieurs façons, l'utilisateur du TAD ne s'en\nsoucie pas."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "pile-vide"
      ],
      "title": "Pile vide",
      "statement": "Que faut-il faire avant de tenter un `pop()` sur une\npile ?",
      "options": [
        {
          "text": "Recalculer la longueur",
          "correct": false,
          "feedback": "Erreur : pas de calcul à faire avant un pop.\n"
        },
        {
          "text": "Vérifier qu'elle n'est pas vide (par exemple avec `is_empty()`)",
          "correct": true,
          "feedback": "Bonne réponse : un `pop` sur pile vide provoque\nen général une erreur (`IndexError` en Python). Il\nfaut toujours vérifier d'abord, ou capturer\nl'exception.\n"
        },
        {
          "text": "Toujours appeler `peek()` d'abord",
          "correct": false,
          "feedback": "Erreur : `peek()` consulte mais ne protège pas\nd'un pop sur une pile vide.\n"
        },
        {
          "text": "Multiplier la taille par deux",
          "correct": false,
          "feedback": "Erreur : aucun rapport. Aucune opération de\nredimensionnement n'est nécessaire avant un pop.\n"
        }
      ],
      "explanation": "Bonne pratique : encapsuler la pile dans une classe\nqui lève une exception explicite (`PileVideError`)\nplutôt que d'exposer l'erreur Python brute. Cela\nrend le code utilisateur plus clair."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "comparaison-tableau"
      ],
      "title": "Liste chaînée vs tableau",
      "statement": "Quel est l'**avantage principal** d'une liste chaînée\nsur un tableau dynamique ?",
      "options": [
        {
          "text": "L'accès indexé est plus rapide",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse. Le tableau a un accès\n$O(1)$, la liste chaînée $O(n)$.\n"
        },
        {
          "text": "L'insertion et la suppression en tête se font en temps constant, sans avoir à décaler d'autres éléments",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'avantage clé. Avec un\ntableau, insérer en tête demande de décaler tous\nles éléments. Avec une liste chaînée, on\nmodifie juste un pointeur.\n"
        },
        {
          "text": "Elle utilise moins de mémoire",
          "correct": false,
          "feedback": "Erreur : c'est l'inverse, en général. Une liste\nchaînée stocke un pointeur supplémentaire par\nélément, ce qui consomme plus de mémoire.\n"
        },
        {
          "text": "Elle est intrinsèquement triée",
          "correct": false,
          "feedback": "Erreur : aucun tri automatique.\n"
        }
      ],
      "explanation": "Compromis classique : tableau pour l'accès indexé\nrapide ; liste chaînée pour les insertions/suppressions\nfréquentes en début ou milieu (avec un pointeur sur\nl'emplacement)."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "implementation"
      ],
      "title": "Implémentation orientée objet d'une pile",
      "statement": "En Python, dans une classe `Pile` minimale, quelles\nméthodes définit-on typiquement ?",
      "options": [
        {
          "text": "`get`, `set` indexés",
          "correct": false,
          "feedback": "Erreur : ce serait un tableau, pas une pile.\n"
        },
        {
          "text": "`add`, `remove`, `find`, `sort`",
          "correct": false,
          "feedback": "Erreur : ces méthodes ne correspondent pas à\nl'API d'une pile. Une pile n'offre pas de `find`\nou `sort`.\n"
        },
        {
          "text": "Une seule méthode `do()`",
          "correct": false,
          "feedback": "Erreur : trop minimaliste. Une pile expose\nplusieurs opérations distinctes.\n"
        },
        {
          "text": "`empiler(x)`, `depiler()`, `sommet()` (ou `peek`), `est_vide()`",
          "correct": true,
          "feedback": "Bonne réponse : ces quatre méthodes constituent\nl'API minimale d'une pile. Souvent, on ajoute\n`taille()` ou `__len__()` pour des raisons\npratiques.\n"
        }
      ],
      "explanation": "Encapsulation : la liste sous-jacente est privée\n(`self._tab` ou `self.__tab`). L'utilisateur ne\nmanipule que les méthodes de la pile, pas la liste.\nC'est ce qui distingue une pile abstraite d'un\ntableau libre."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "equilibrage-parentheses"
      ],
      "title": "Détection de parenthèses déséquilibrées",
      "statement": "L'expression `(())` est-elle bien parenthésée ? Et\n`(()` ?",
      "options": [
        {
          "text": "Toutes deux sont bien parenthésées",
          "correct": false,
          "feedback": "Erreur : `(()` ne ferme pas la première\nparenthèse. Donc déséquilibrée.\n"
        },
        {
          "text": "La parité du nombre de caractères suffit à juger",
          "correct": false,
          "feedback": "Erreur : `()(` est de longueur paire mais\ndéséquilibré. La parité ne suffit pas.\n"
        },
        {
          "text": "`(())` est correcte ; `(()` est incorrecte (la première parenthèse n'est jamais fermée)",
          "correct": true,
          "feedback": "Bonne réponse : avec une pile, on empile chaque\n`(`, on dépile à chaque `)`. À la fin de la\ndeuxième chaîne, la pile contient encore une\nparenthèse non fermée, donc déséquilibrée.\n"
        },
        {
          "text": "Toutes deux sont incorrectes",
          "correct": false,
          "feedback": "Erreur : `(())` est parfaitement équilibrée :\ndeux parenthèses ouvrantes, deux fermantes, dans\nle bon ordre.\n"
        }
      ],
      "explanation": "Règle algorithmique : une expression est bien\nparenthésée si et seulement si la pile est vide à la\nfin, ET qu'aucune fermante n'apparaît sur une pile\nvide pendant le parcours."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "code",
        "analyse"
      ],
      "title": "Analyse d'un code de pile",
      "statement": "Que renvoie le code Python suivant ?\n\n```\npile = []\nfor x in [1, 2, 3, 4]:\n    pile.append(x)\nprint(pile.pop())\n```",
      "options": [
        {
          "text": "$0$",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec $0$.\n"
        },
        {
          "text": "La liste entière `[1, 2, 3]`",
          "correct": false,
          "feedback": "Erreur : `pop()` ne retourne qu'un seul élément.\n"
        },
        {
          "text": "$1$",
          "correct": false,
          "feedback": "Erreur : c'est l'élément de **bas** de pile (le\npremier empilé). Mais `pop()` retire le **sommet**\n(le dernier empilé).\n"
        },
        {
          "text": "$4$",
          "correct": true,
          "feedback": "Bonne réponse : on a empilé $1, 2, 3, 4$. Le\nsommet est $4$. `pop()` retire et renvoie $4$.\n"
        }
      ],
      "explanation": "Comportement standard LIFO : dernier entré, premier\nsorti. Si on continuait à `pop()` plusieurs fois, on\nobtiendrait $4, 3, 2, 1$ dans l'ordre."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "implementation",
        "deque"
      ],
      "title": "Pourquoi pas list.pop(0) pour une file",
      "statement": "Pourquoi `list.pop(0)` est-il **inefficace** pour\nimplémenter une file en Python ?",
      "options": [
        {
          "text": "Parce que les listes Python ne sont pas mutables",
          "correct": false,
          "feedback": "Erreur : les listes sont mutables.\n"
        },
        {
          "text": "Parce qu'il faut décaler tous les autres éléments d'un cran vers la gauche, ce qui est en $O(n)$",
          "correct": true,
          "feedback": "Bonne réponse : les listes Python sont des\ntableaux dynamiques contigus en mémoire. Retirer\nle premier élément force à recopier tous les\nautres. Pour une file efficace, utiliser\n`collections.deque` (deux pointeurs internes,\n$O(1)$ aux deux bouts).\n"
        },
        {
          "text": "Parce que ça lève toujours une erreur",
          "correct": false,
          "feedback": "Erreur : `pop(0)` fonctionne sur une liste non\nvide. Le problème est la performance, pas la\ncorrection.\n"
        },
        {
          "text": "Parce que `pop` ne peut pas prendre d'argument",
          "correct": false,
          "feedback": "Erreur : `pop(i)` accepte un argument optionnel.\n"
        }
      ],
      "explanation": "C'est un piège classique en Python. `deque` (de\n*double-ended queue*) est l'outil adapté pour des\nfiles efficaces, avec `append` à droite et\n`popleft` à gauche, tous deux en $O(1)$."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "tampon"
      ],
      "title": "Tampon circulaire",
      "statement": "Un **tampon circulaire** (*ring buffer*) est :",
      "options": [
        {
          "text": "Un dictionnaire trié",
          "correct": false,
          "feedback": "Un dictionnaire associe\ndes clés à des valeurs et\nn'a pas de notion de\ntampon ni de circularité.\nLe tampon circulaire est\nquant à lui une file de\ntaille fixe utilisée pour\ndes flux continus.\n"
        },
        {
          "text": "Une liste chaînée fermée",
          "correct": false,
          "feedback": "Erreur : c'est plutôt une liste circulaire, pas un\ntampon circulaire.\n"
        },
        {
          "text": "Une file implémentée sur un tableau de taille fixe, où l'on revient au début quand on dépasse la fin",
          "correct": true,
          "feedback": "Bonne réponse : très utilisé pour des buffers\ntemps-réel (audio, vidéo, journaux d'événements).\nComme la taille est fixe, on évite les\nallocations dynamiques, et la « circularité »\npermet d'utiliser tout l'espace disponible.\n"
        },
        {
          "text": "Une pile de taille variable",
          "correct": false,
          "feedback": "Un tampon circulaire\nn'est pas une pile : il\nimplémente une file (au\nsens FIFO) sur un\ntableau de taille fixe,\ncomme expliqué dans la\nbonne réponse.\n"
        }
      ],
      "explanation": "Le tampon circulaire est très efficace : pas\nd'allocation dynamique, mémoire prévisible. Mais sa\ntaille étant fixe, il faut gérer le cas où il est\nplein (écraser les anciens, ou refuser le nouveau)."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "pile-recursion"
      ],
      "title": "Récursivité et pile",
      "statement": "Toute fonction récursive peut être convertie en une\nversion itérative utilisant explicitement :",
      "options": [
        {
          "text": "Une file",
          "correct": false,
          "feedback": "Erreur : la récursivité a une nature LIFO (l'appel\nle plus profond est traité en premier). C'est\ndonc une pile.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : un dictionnaire ne préserve pas l'ordre\nd'appels. La structure adaptée est une pile.\n"
        },
        {
          "text": "Une pile, qui simule la pile d'appels gérée implicitement par le langage",
          "correct": true,
          "feedback": "Bonne réponse : c'est précisément ce que fait le\nprocesseur en interne. En remplaçant les appels\nrécursifs par des opérations sur une pile\nexplicite, on transforme une fonction récursive\nen boucle. Cela peut éviter les dépassements de\npile.\n"
        },
        {
          "text": "Un arbre",
          "correct": false,
          "feedback": "Erreur : un arbre est une donnée, pas un\nmécanisme d'exécution.\n"
        }
      ],
      "explanation": "Cette technique de **dérécursivation** est utile\nquand la profondeur de récursion dépasse la limite\nPython (souvent $1000$). On remplace les appels par\ndes push/pop sur une pile explicite, et on contrôle\nainsi la mémoire utilisée."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "file-priorite"
      ],
      "title": "File de priorité",
      "statement": "Une **file de priorité** (priority queue) diffère d'une\nfile FIFO classique en ce que :",
      "options": [
        {
          "text": "Elle ne stocke que les nombres",
          "correct": false,
          "feedback": "Erreur : on peut y stocker n'importe quel type\nd'élément, à condition d'avoir un critère de\npriorité.\n"
        },
        {
          "text": "Aucune différence",
          "correct": false,
          "feedback": "Erreur : la différence est essentielle.\n"
        },
        {
          "text": "Elle est plus rapide que la file ordinaire",
          "correct": false,
          "feedback": "Erreur : elle est généralement plus lente\n($O(\\log n)$ vs $O(1)$). L'avantage est\nfonctionnel, pas la rapidité.\n"
        },
        {
          "text": "L'élément retiré est celui qui a la priorité la plus élevée, pas forcément le plus ancien",
          "correct": true,
          "feedback": "Bonne réponse : par exemple, dans un hôpital, on\ntraite d'abord les cas les plus urgents, pas\nceux arrivés en premier. Implémentation typique :\nun **tas binaire** (heap), avec opérations en\n$O(\\log n)$.\n"
        }
      ],
      "explanation": "Files de priorité utilisées dans : Dijkstra (plus\ncourts chemins), algorithme A*, ordonnancement par\npriorité, simulation d'événements discrets. En\nPython : module `heapq`."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Choisir la bonne structure",
      "statement": "On souhaite gérer l'historique des actions d'un éditeur\nde texte, avec une fonction « annuler » (Ctrl+Z). Quelle\nstructure est la plus adaptée ?",
      "options": [
        {
          "text": "Une liste triée",
          "correct": false,
          "feedback": "Erreur : pas de tri à faire ; l'ordre est déjà\ndonné par l'arrivée.\n"
        },
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : pas de notion temporelle adaptée.\n"
        },
        {
          "text": "Une pile",
          "correct": true,
          "feedback": "Bonne réponse : à chaque action, on l'empile.\nCtrl+Z dépile et annule la dernière. C'est le cas\nd'usage typique des piles. Pour le « refaire »\n(Ctrl+Y), on utilise une **deuxième** pile.\n"
        },
        {
          "text": "Une file",
          "correct": false,
          "feedback": "Erreur : la file annulerait la **première** action,\npas la dernière. Or « annuler » concerne la plus\nrécente.\n"
        }
      ],
      "explanation": "Petit défi : implémenter Ctrl+Z **et** Ctrl+Y. Astuce\nclassique : deux piles. Quand on annule, on dépile de\nla pile « historique » et on empile dans la pile\n« refait ». Quand on refait, c'est l'inverse."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "maillon-implementation"
      ],
      "title": "Maillon d'une liste chaînée",
      "statement": "On souhaite implémenter une liste simplement chaînée\nen Python. Quelle classe représente un **maillon**\ncorrectement ?",
      "options": [
        {
          "text": "```\nclass Maillon:\n    def __init__(self, valeur):\n        self.valeur = valeur\n```\n",
          "correct": false,
          "feedback": "Erreur : il manque l'attribut `suivant`. Sans\nlui, le maillon ne peut pas pointer vers le\nsuivant et la « chaîne » est cassée. Ce serait\njuste un conteneur de valeur, pas un maillon.\n"
        },
        {
          "text": "```\nclass Maillon:\n    def __init__(self, valeur, suivant=None):\n        self.valeur = valeur\n        self.suivant = suivant\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : un maillon contient sa propre\nvaleur et une référence vers le maillon suivant\n(ou `None` si c'est le dernier). C'est la\ndéfinition canonique. La construction d'une\nliste chaînée se fait ensuite en chaînant\nplusieurs maillons via leur attribut `suivant`.\n"
        },
        {
          "text": "```\ndef maillon(valeur):\n    return [valeur]\n```\n",
          "correct": false,
          "feedback": "Erreur : on retourne ici une liste Python avec\nun seul élément, ce qui n'est pas un maillon.\nPour chaîner plusieurs valeurs, il faudrait\nimbriquer des listes, ce qui est lourd et peu\nclair par rapport à une vraie classe.\n"
        },
        {
          "text": "```\nclass Maillon:\n    def __init__(self, valeur, precedent, suivant):\n        self.valeur = valeur\n        self.precedent = precedent\n        self.suivant = suivant\n```\n",
          "correct": false,
          "feedback": "Erreur : on a ici un maillon **doublement\nchaîné** (avec un pointeur vers le précédent et\nun vers le suivant). C'est utile pour certaines\nstructures, mais la question portait sur une\nliste **simplement** chaînée.\n"
        }
      ],
      "explanation": "Pour parcourir la liste chaînée, on commence par la\ntête et on suit les pointeurs `suivant` jusqu'à\natteindre `None`. La classe `Maillon` est l'élément\natomique ; la classe `ListeChainee` (ou directement\nune variable « tête ») gère la collection."
    },
    {
      "id": "q27",
      "difficulty": 2,
      "skills": [
        "parcours-profondeur"
      ],
      "title": "Parcours en profondeur avec pile explicite",
      "statement": "Pour effectuer un parcours en profondeur d'un arbre\nou d'un graphe **sans utiliser la récursivité**, quel\nalgorithme et quelle structure utilise-t-on ?",
      "options": [
        {
          "text": "On utilise une pile : on empile le sommet de\ndépart, puis on répète : dépiler un sommet, le\nmarquer comme visité, et empiler ses voisins\nnon visités. La pile reproduit l'ordre LIFO de\nla récursion\n",
          "correct": true,
          "feedback": "Bonne réponse : la pile explicite simule\nexactement ce que ferait la récursion via la\npile d'appels du langage. C'est un cas concret\nde dérécursivation. Avantage : on contrôle la\nmémoire et on évite les `RecursionError` sur\nles graphes profonds.\n"
        },
        {
          "text": "On utilise un dictionnaire pour stocker les\nsommets visités\n",
          "correct": false,
          "feedback": "Réponse partielle : un dictionnaire (ou un\nensemble) sert effectivement à mémoriser les\nsommets déjà visités, mais ce n'est pas la\nstructure qui dirige le parcours. C'est la\npile (ou la file) qui décide de l'ordre de\nvisite.\n"
        },
        {
          "text": "On utilise une file : on enfile le sommet\nde départ et on défile à chaque étape\n",
          "correct": false,
          "feedback": "Erreur : avec une file (FIFO), on obtient un\nparcours en **largeur**, pas en profondeur.\nLe parcours en profondeur explore d'abord\nles descendants avant les frères, ce qui est\nun comportement LIFO.\n"
        },
        {
          "text": "On trie d'abord les sommets puis on les parcourt\ndans l'ordre\n",
          "correct": false,
          "feedback": "Erreur : on parle ici d'un parcours qui\nrespecte la structure du graphe ou de l'arbre,\npas d'un parcours par valeur croissante. Trier\nn'a pas de sens dans ce contexte.\n"
        }
      ],
      "explanation": "Mémoire récurrente :\n- **Pile** + parcours = parcours en **profondeur**.\n- **File** + parcours = parcours en **largeur**.\nCette dualité s'observe sur les arbres comme sur\nles graphes, et illustre la puissance des\nstructures linéaires LIFO/FIFO en algorithmique."
    },
    {
      "id": "q28",
      "difficulty": 3,
      "skills": [
        "annuler-refaire"
      ],
      "title": "Annuler et refaire avec deux piles",
      "statement": "On veut implémenter une fonctionnalité « annuler »\n(Ctrl+Z) et « refaire » (Ctrl+Y) dans un éditeur.\nQuelle structure utiliser ?",
      "options": [
        {
          "text": "Un dictionnaire indexé par l'instant de l'action\n",
          "correct": false,
          "feedback": "Solution surdimensionnée : un dictionnaire\npermet d'aller à n'importe quel instant, ce\nqui n'est pas demandé pour Ctrl+Z/Ctrl+Y. Les\ndeux opérations agissent toujours sur la\ndernière action ou la dernière action\nannulée, ce qui correspond à des piles.\n"
        },
        {
          "text": "Une file pour l'historique, une pile pour le\nrefaire\n",
          "correct": false,
          "feedback": "Erreur : une file annulerait l'action la plus\nancienne (FIFO), alors qu'on veut annuler la\nplus récente (LIFO). Il faut donc une **pile**\npour l'historique, pas une file.\n"
        },
        {
          "text": "Deux piles : une pile « historique » qui contient\nles actions déjà effectuées, et une pile\n« refaire » qui contient les actions annulées.\nAnnuler = dépiler de l'historique, empiler dans\nrefaire ; refaire = dépiler de refaire,\nempiler dans l'historique. Toute nouvelle\naction vide la pile « refaire »\n",
          "correct": true,
          "feedback": "Bonne réponse : c'est le schéma standard des\néditeurs de texte. Les deux piles évoluent\nsymétriquement, sauf au moment d'une nouvelle\naction, qui rend impossible de « refaire » les\nactions annulées précédemment (c'est le\ncomportement attendu : on ne peut pas refaire\nce qui n'existe plus dans la branche\nd'historique courante).\n"
        },
        {
          "text": "Une seule pile suffit\n",
          "correct": false,
          "feedback": "Erreur : avec une seule pile, on peut annuler\nmais on perd définitivement les actions\nannulées. Pour pouvoir les **refaire**, il\nfaut les conserver quelque part : c'est le\nrôle de la deuxième pile.\n"
        }
      ],
      "explanation": "Variante avancée : pour gérer un historique\narborescent (où chaque action peut produire une\nbranche), il faut utiliser un **arbre**, pas\nseulement deux piles. C'est le modèle utilisé par\ncertains éditeurs avancés (vim, Emacs en mode\n`undo-tree`)."
    }
  ]
}