{
  "chapter": {
    "id": "graphes",
    "level": "terminale",
    "theme": "Structures de données",
    "title": "Graphes",
    "description": "Définition d'un graphe (sommets, arêtes), graphes orientés\net non orientés, pondérés, représentations (matrice\nd'adjacence, liste d'adjacence), parcours en largeur\net en profondeur, plus court chemin, applications.",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Définition d'un graphe",
      "statement": "Qu'est-ce qu'un **graphe** en informatique ?",
      "options": [
        {
          "text": "Un type d'arbre",
          "correct": false,
          "feedback": "Erreur : c'est plutôt l'inverse. Un arbre est un\ncas particulier de graphe (acyclique connexe).\n"
        },
        {
          "text": "Une représentation visuelle de données numériques (camembert, histogramme)",
          "correct": false,
          "feedback": "Erreur : ce serait un **graphique**. Un graphe en\ninformatique est une structure mathématique\ndifférente.\n"
        },
        {
          "text": "Un ensemble de sommets (ou nœuds) reliés par des arêtes (ou arcs si orientés)",
          "correct": true,
          "feedback": "Bonne réponse : c'est la définition mathématique\nstandard. Un graphe modélise n'importe quelle\nrelation entre objets : réseau routier, réseau\nsocial, dépendances de tâches, etc.\n"
        },
        {
          "text": "Un dictionnaire trié",
          "correct": false,
          "feedback": "Un dictionnaire est une\nstructure associant des\nclés à des valeurs, sans\nrapport avec un ensemble\nde sommets reliés par des\narêtes.\n"
        }
      ],
      "explanation": "Les graphes sont omniprésents : transport, réseaux\nsociaux, web (pages reliées par hyperliens), réseaux\nélectriques, planification de tâches, recommandations.\nConnaître les algorithmes de graphes est essentiel."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "oriente"
      ],
      "title": "Graphe orienté",
      "statement": "Quelle est la différence entre un graphe **orienté** et\nun graphe **non orienté** ?",
      "options": [
        {
          "text": "Dans un graphe orienté, chaque arête a un sens (de A vers B), alors que dans un graphe non orienté, l'arête relie A et B sans préférence",
          "correct": true,
          "feedback": "Bonne réponse : on parle d'**arcs** pour les\narêtes orientées. Exemple orienté : suivre\nquelqu'un sur Twitter (asymétrique). Exemple non\norienté : amitié sur Facebook (symétrique).\n"
        },
        {
          "text": "Un graphe orienté n'a pas de cycle",
          "correct": false,
          "feedback": "Erreur : un graphe orienté **peut** avoir des\ncycles. Sans cycle, on parle de DAG (graphe\norienté acyclique).\n"
        },
        {
          "text": "Le graphe orienté contient plus de sommets",
          "correct": false,
          "feedback": "Erreur : aucun rapport avec le nombre de sommets.\n"
        },
        {
          "text": "Un graphe non orienté est plus rapide à parcourir",
          "correct": false,
          "feedback": "Erreur : la vitesse de parcours dépend du nombre\nde sommets et arêtes, pas de l'orientation.\n"
        }
      ],
      "explanation": "Cas typique : réseau routier (orienté si rues à sens\nunique, non orienté si toutes en double sens). Le\nweb est orienté (un lien va d'une page à l'autre\nsans symétrie)."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "pondere"
      ],
      "title": "Graphe pondéré",
      "statement": "Qu'est-ce qu'un graphe **pondéré** ?",
      "options": [
        {
          "text": "Un graphe avec beaucoup d'arêtes",
          "correct": false,
          "feedback": "Erreur : c'est plutôt un graphe **dense**. Aucun\nrapport avec « pondéré ».\n"
        },
        {
          "text": "Un graphe partagé entre plusieurs utilisateurs",
          "correct": false,
          "feedback": "Le partage entre\nutilisateurs relève d'un\naspect logiciel sans\nrapport avec la\npondération des arêtes.\n"
        },
        {
          "text": "Un graphe trop lourd à stocker en mémoire",
          "correct": false,
          "feedback": "Erreur : « pondéré » est un terme technique précis,\npas une métaphore.\n"
        },
        {
          "text": "Un graphe où chaque arête porte un poids (valeur numérique), représentant par exemple une distance, un coût, un temps",
          "correct": true,
          "feedback": "Bonne réponse : par exemple, un GPS représente\nles routes par un graphe pondéré (poids = temps\nde trajet). Les algorithmes de plus court chemin\n(Dijkstra) exploitent ces poids.\n"
        }
      ],
      "explanation": "Un graphe peut être à la fois orienté ET pondéré\n(cas d'un GPS, d'un réseau de transport, etc.)."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "matrice-adjacence"
      ],
      "title": "Matrice d'adjacence",
      "statement": "Une **matrice d'adjacence** d'un graphe à $n$ sommets est :",
      "options": [
        {
          "text": "Une matrice $n \\times n$ où $M[i][j] = 1$ s'il existe une arête entre $i$ et $j$, sinon $0$ (ou le poids de l'arête si pondéré)",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'une des deux représentations\nprincipales des graphes. Avantage : test\nd'arête en $O(1)$. Inconvénient : consomme\n$O(n^2)$ d'espace, même pour des graphes creux.\n"
        },
        {
          "text": "Un tableau $1 \\times n$ contenant les degrés",
          "correct": false,
          "feedback": "Erreur : ce serait un tableau de degrés, pas une\nmatrice d'adjacence.\n"
        },
        {
          "text": "Un arbre",
          "correct": false,
          "feedback": "Un arbre est une\nstructure de données\nspécifique, sans rapport\navec la représentation\nd'un graphe par une\nmatrice.\n"
        },
        {
          "text": "Un dictionnaire de listes",
          "correct": false,
          "feedback": "Erreur : c'est plutôt une **liste d'adjacence**.\n"
        }
      ],
      "explanation": "Pour un graphe non orienté, la matrice est\n**symétrique** ($M[i][j] = M[j][i]$). Pour un\ngraphe orienté, elle peut ne pas l'être."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "liste-adjacence"
      ],
      "title": "Liste d'adjacence",
      "statement": "Une **liste d'adjacence** d'un graphe consiste à :",
      "options": [
        {
          "text": "Stocker uniquement les sommets isolés",
          "correct": false,
          "feedback": "Erreur : on stocke tous les sommets.\n"
        },
        {
          "text": "Trier les sommets par degré",
          "correct": false,
          "feedback": "Erreur : aucun tri requis.\n"
        },
        {
          "text": "Numéroter les arêtes de $1$ à $m$",
          "correct": false,
          "feedback": "Erreur : c'est plutôt une numérotation, pas une\nliste d'adjacence.\n"
        },
        {
          "text": "Stocker pour chaque sommet la liste de ses voisins",
          "correct": true,
          "feedback": "Bonne réponse : pour le sommet $v$, on stocke la\nliste de tous les sommets directement reliés à\nlui. Plus efficace en mémoire que la matrice\nd'adjacence pour les graphes **creux**\n(peu d'arêtes par sommet). Espace : $O(n + m)$\navec $m$ arêtes.\n"
        }
      ],
      "explanation": "Choix entre matrice et liste : matrice pour graphes\n**denses** (peu de zéros) ou si on a besoin de\ntester très souvent l'existence d'une arête. Liste\npour graphes **creux** (cas le plus fréquent en\npratique)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "bfs"
      ],
      "title": "Parcours en largeur",
      "statement": "Le parcours en **largeur** d'un graphe :",
      "options": [
        {
          "text": "Visite d'abord tous les voisins du sommet de départ, puis tous leurs voisins non visités, et ainsi de suite par niveaux",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'algorithme de référence\npour explorer un graphe en respectant la distance\nen nombre d'arêtes. Implémentation : avec une\n**file** (FIFO).\n"
        },
        {
          "text": "Parcourt la branche la plus profonde d'abord",
          "correct": false,
          "feedback": "Erreur : c'est le parcours en **profondeur**\n(parcours en profondeur).\n"
        },
        {
          "text": "Trie les sommets par valeur",
          "correct": false,
          "feedback": "Erreur : aucun tri par valeur.\n"
        },
        {
          "text": "Visite uniquement les feuilles",
          "correct": false,
          "feedback": "Erreur : tous les sommets accessibles sont\nvisités.\n"
        }
      ],
      "explanation": "Application clé : trouver le plus court chemin\n**en nombre d'arêtes** depuis un sommet de départ.\nAussi utilisé pour tester la connexité, calculer\nles composantes connexes, etc."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "dfs"
      ],
      "title": "Parcours en profondeur",
      "statement": "Le parcours en **profondeur** d'un graphe :",
      "options": [
        {
          "text": "Explore aussi loin que possible une branche avant de revenir en arrière pour explorer une autre",
          "correct": true,
          "feedback": "Bonne réponse : implémentation typique avec une\n**pile** (explicite ou implicite via la\nrécursivité). Le parcours en profondeur est\nutile pour la détection de cycles, le tri\ntopologique, la recherche de composantes\nfortement connexes, etc.\n"
        },
        {
          "text": "Trie les sommets par profondeur",
          "correct": false,
          "feedback": "Erreur : aucun tri par profondeur.\n"
        },
        {
          "text": "Visite tous les voisins du sommet de départ avant d'aller plus loin",
          "correct": false,
          "feedback": "Erreur : c'est le parcours en **largeur**.\n"
        },
        {
          "text": "Calcule la longueur totale du graphe",
          "correct": false,
          "feedback": "Erreur : aucune notion de longueur globale.\n"
        }
      ],
      "explanation": "Mémo : **largeur = file = FIFO** ; **profondeur =\npile = LIFO**. Les deux parcours visitent tous les\nsommets accessibles, mais dans un ordre différent."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "structure-bfs"
      ],
      "title": "Structure utilisée par parcours en largeur",
      "statement": "Pour implémenter un parcours en largeur, quelle structure\nde données utilise-t-on naturellement ?",
      "options": [
        {
          "text": "Un dictionnaire",
          "correct": false,
          "feedback": "Erreur : un dictionnaire n'a pas la sémantique\nd'ordre nécessaire.\n"
        },
        {
          "text": "Une pile",
          "correct": false,
          "feedback": "Erreur : la pile correspond au parcours en\n**profondeur**.\n"
        },
        {
          "text": "Une file (FIFO)",
          "correct": true,
          "feedback": "Bonne réponse : on enfile le sommet de départ,\npuis tant que la file n'est pas vide on défile\nun sommet, on visite, et on enfile ses voisins\nnon encore visités. C'est exactement le parcours en largeur.\n"
        },
        {
          "text": "Aucune structure",
          "correct": false,
          "feedback": "Erreur : il faut bien stocker les sommets en\nattente.\n"
        }
      ],
      "explanation": "En Python : `from collections import deque ;\nf = deque([depart])`. Avec `f.append(v)` pour enfiler\net `f.popleft()` pour défiler, en $O(1)$ chacun."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "structure-dfs"
      ],
      "title": "Structure utilisée par parcours en profondeur",
      "statement": "Le parcours en profondeur peut s'implémenter en utilisant :",
      "options": [
        {
          "text": "Une file uniquement",
          "correct": false,
          "feedback": "Erreur : la file donne un parcours en largeur,\npas en profondeur.\n"
        },
        {
          "text": "Un graphe imbriqué",
          "correct": false,
          "feedback": "Cette description ne\ncorrespond pas à une\nstructure standard\nutilisée pour le\nparcours d'un graphe.\n"
        },
        {
          "text": "Un arbre binaire",
          "correct": false,
          "feedback": "Erreur : un arbre est une structure différente,\npas l'outil de parcours.\n"
        },
        {
          "text": "Une pile (explicite ou la pile d'appels via la récursivité)",
          "correct": true,
          "feedback": "Bonne réponse : la récursivité utilise\nimplicitement la pile d'appels. La version\nitérative utilise explicitement une pile (`list`\navec `append` et `pop`).\n"
        }
      ],
      "explanation": "Récursivité = pile implicite. Itératif avec pile =\npile explicite. Les deux donnent le même résultat\n(à l'ordre des voisins près). La version récursive\nest souvent plus claire."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "chemin"
      ],
      "title": "Plus court chemin",
      "statement": "Pour trouver le plus court chemin (en nombre d'arêtes)\nentre deux sommets dans un graphe **non pondéré**, on\nutilise :",
      "options": [
        {
          "text": "Un tri topologique",
          "correct": false,
          "feedback": "Erreur : c'est utile pour les DAG, pas pour les\nplus courts chemins.\n"
        },
        {
          "text": "Un parcours en largeur",
          "correct": true,
          "feedback": "Bonne réponse : le parcours en largeur visite les sommets par\nordre croissant de distance (en arêtes) depuis\nle sommet de départ. Le premier moment où on\natteint le sommet d'arrivée donne la distance\nminimale.\n"
        },
        {
          "text": "Un parcours en profondeur",
          "correct": false,
          "feedback": "Erreur : parcours en profondeur ne donne pas le plus court chemin\nen général. Il peut prendre une route longue.\n"
        },
        {
          "text": "L'algorithme de Dijkstra",
          "correct": false,
          "feedback": "Erreur : Dijkstra est pour les graphes\n**pondérés** (poids positifs). Pour un graphe non\npondéré, parcours en largeur suffit.\n"
        }
      ],
      "explanation": "Pour graphe pondéré à poids **positifs** : Dijkstra\n($O((n + m) \\log n)$). Pour graphe avec poids\nnégatifs : Bellman-Ford ($O(nm)$). parcours en largeur s'utilise\nquand toutes les arêtes ont un poids identique\n(typiquement, $1$)."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "degre"
      ],
      "title": "Degré d'un sommet",
      "statement": "Le **degré** d'un sommet dans un graphe non orienté\nest :",
      "options": [
        {
          "text": "Le nombre d'arêtes incidentes à ce sommet (i.e. le nombre de voisins).",
          "correct": true,
          "feedback": "Bonne réponse : par exemple, dans le graphe\n$\\{(A, B), (A, C), (B, C)\\}$, le sommet $A$ est\nde degré $2$. Pour un graphe orienté, on\ndistingue le **degré entrant** et le **degré\nsortant**.\n"
        },
        {
          "text": "Le poids de l'arête la plus lourde incidente au sommet",
          "correct": false,
          "feedback": "Le degré d'un sommet\nest un nombre d'arêtes,\net non un poids. Le\npoids n'intervient que\ndans les graphes\npondérés, et n'est pas\nlié à la notion de\ndegré.\n"
        },
        {
          "text": "La distance jusqu'à la racine",
          "correct": false,
          "feedback": "Erreur : il n'y a pas de racine dans un graphe\ngénéral. Confusion avec les arbres.\n"
        },
        {
          "text": "Sa profondeur dans un parcours",
          "correct": false,
          "feedback": "Erreur : la profondeur est une notion liée au\nparcours, pas au sommet en soi.\n"
        }
      ],
      "explanation": "Théorème classique (Euler) : la somme des degrés\nd'un graphe non orienté est $2m$ (deux fois le\nnombre d'arêtes), car chaque arête contribue $1$ à\ndeux sommets."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "chemin-cycle"
      ],
      "title": "Cycle dans un graphe",
      "statement": "Un **cycle** dans un graphe est :",
      "options": [
        {
          "text": "Un sommet isolé",
          "correct": false,
          "feedback": "Erreur : un sommet isolé n'a aucune arête. Ce\nn'est pas un cycle.\n"
        },
        {
          "text": "Une arête répétée",
          "correct": false,
          "feedback": "Erreur : c'est un multi-graphe, notion\ndifférente.\n"
        },
        {
          "text": "Un sommet de degré pair",
          "correct": false,
          "feedback": "Erreur : pas la définition d'un cycle.\n"
        },
        {
          "text": "Une suite d'arêtes qui revient à son point de départ",
          "correct": true,
          "feedback": "Bonne réponse : par exemple, le chemin\n$A \\to B \\to C \\to A$ forme un cycle. Détecter\nles cycles est important : un graphe sans cycle\nest appelé **acyclique** (DAG si orienté).\n"
        }
      ],
      "explanation": "Un **arbre** est un graphe connexe sans cycle. C'est\ncette absence de cycle qui caractérise l'arbre par\nrapport au graphe général."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "connexite"
      ],
      "title": "Graphe connexe",
      "statement": "Un graphe non orienté est **connexe** quand :",
      "options": [
        {
          "text": "Il est orienté",
          "correct": false,
          "feedback": "Erreur : la connexité concerne les graphes\norientés ET non orientés.\n"
        },
        {
          "text": "Il a beaucoup d'arêtes",
          "correct": false,
          "feedback": "Erreur : la quantité ne suffit pas. C'est la\nstructure qui compte.\n"
        },
        {
          "text": "Il existe un chemin entre toute paire de sommets",
          "correct": true,
          "feedback": "Bonne réponse : on peut « atteindre » n'importe\nquel sommet depuis n'importe quel autre. Si on\nenlève cette propriété, on peut avoir plusieurs\n**composantes connexes** (groupes isolés).\n"
        },
        {
          "text": "Il a au moins $n$ arêtes",
          "correct": false,
          "feedback": "Erreur : un graphe connexe à $n$ sommets a au\nmoins $n - 1$ arêtes (cas de l'arbre), mais\ncela n'est pas suffisant.\n"
        }
      ],
      "explanation": "Le parcours en largeur ou parcours en profondeur depuis un sommet visite toute sa\n**composante connexe**. Si on n'atteint pas tous les\nsommets, le graphe n'est pas connexe."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "dag"
      ],
      "title": "DAG",
      "statement": "Que signifie l'acronyme **DAG** ?",
      "options": [
        {
          "text": "Décomposition d'Algorithme par Graphe",
          "correct": false,
          "feedback": "Erreur : sigle inventé.\n"
        },
        {
          "text": "Distribution Aléatoire de Graphes",
          "correct": false,
          "feedback": "Erreur : sigle inventé.\n"
        },
        {
          "text": "Données Algorithmiques Génériques",
          "correct": false,
          "feedback": "Erreur : sigle inventé.\n"
        },
        {
          "text": "Directed Acyclic Graph (graphe orienté acyclique)",
          "correct": true,
          "feedback": "Bonne réponse : un DAG est un graphe orienté\nsans cycle. Très utile pour modéliser des\ndépendances : tâches d'un projet, dépendances\nde modules, généalogie, calculs, etc.\n"
        }
      ],
      "explanation": "Sur un DAG, le **tri topologique** est possible :\nranger les sommets dans un ordre tel que chaque\narête $u \\to v$ va d'un sommet plus tôt vers un\nsommet plus tard. Utilisé pour ordonnancer des\ntâches respectant leurs dépendances."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "taille-matrice"
      ],
      "title": "Espace mémoire matrice vs liste",
      "statement": "Pour un graphe à $n = 1000$ sommets et $m = 5000$\narêtes, quelle représentation est la plus économe en\nmémoire ?",
      "options": [
        {
          "text": "La liste d'adjacence ($O(n + m) = 6000$ entrées)",
          "correct": true,
          "feedback": "Bonne réponse : la liste d'adjacence consomme\nde l'ordre du nombre d'arêtes plus le nombre de\nsommets. Beaucoup mieux que la matrice quand le\ngraphe est **creux** ($m \\ll n^2$), ce qui est\nle cas en pratique pour beaucoup d'applications.\n"
        },
        {
          "text": "La matrice d'adjacence ($O(n^2) = 10^6$ cases)",
          "correct": false,
          "feedback": "Erreur : la matrice consomme bien plus que la\nliste pour un graphe creux. La liste vaut mieux.\n"
        },
        {
          "text": "Aucune différence",
          "correct": false,
          "feedback": "Erreur : la différence est importante, surtout\npour les graphes creux.\n"
        },
        {
          "text": "Cela dépend du langage de programmation",
          "correct": false,
          "feedback": "Erreur : la complexité asymptotique est\nindépendante du langage.\n"
        }
      ],
      "explanation": "Densité d'un graphe : $d = m / \\binom{n}{2}$. Pour\n$d$ proche de $1$ (graphe dense), la matrice peut\nêtre préférable. Pour $d$ proche de $0$ (creux), la\nliste est meilleure."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "bfs-distance"
      ],
      "title": "Parcours en largeur et distance",
      "statement": "Avec un parcours en largeur depuis le sommet $s$, on calcule pour chaque\nsommet $v$ atteignable :",
      "options": [
        {
          "text": "Les sommets isolés",
          "correct": false,
          "feedback": "Erreur : parcours en largeur visite seulement les sommets\naccessibles depuis $s$.\n"
        },
        {
          "text": "Tous les chemins possibles",
          "correct": false,
          "feedback": "Erreur : trop d'information. parcours en largeur calcule\nuniquement la distance minimale, pas tous les\nchemins.\n"
        },
        {
          "text": "Le poids minimal du chemin $s \\to v$ (si pondéré)",
          "correct": false,
          "feedback": "Erreur : parcours en largeur calcule la distance en **nombre\nd'arêtes**, pas en poids. Pour les poids,\nutiliser Dijkstra.\n"
        },
        {
          "text": "Le nombre minimal d'arêtes du chemin $s \\to v$",
          "correct": true,
          "feedback": "Bonne réponse : c'est précisément ce que parcours en largeur\ncalcule. Très utile dans des graphes où toutes\nles arêtes sont équivalentes (réseau social,\njeu de plateau, etc.).\n"
        }
      ],
      "explanation": "Au moment où parcours en largeur découvre un sommet, il peut\nenregistrer (a) la distance, (b) le sommet\n« parent » dans le parcours en largeur. Avec cela, on peut\nreconstituer le chemin le plus court en arêtes."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "reseau-social"
      ],
      "title": "Application réseau social",
      "statement": "Sur un réseau social, on veut trouver tous les amis\n« à distance $2$ » (amis d'amis) d'un utilisateur $u$.\nQuel algorithme utiliser ?",
      "options": [
        {
          "text": "Un parcours en largeur, en s'arrêtant à la profondeur $2$",
          "correct": true,
          "feedback": "Bonne réponse : parcours en largeur avec une limite de\nprofondeur. À profondeur $0$ : $u$ ; à $1$ : ses\namis ; à $2$ : les amis des amis (sans compter\n$u$ ni ses amis directs si on veut). Très\nutilisé pour la recommandation d'amis.\n"
        },
        {
          "text": "Un tri des amis par valeur d'identifiant",
          "correct": false,
          "feedback": "Le tri d'une liste n'a\naucun rapport avec la\nrecherche d'amis à une\ndistance donnée dans un\nréseau social.\n"
        },
        {
          "text": "Un parcours en profondeur sans limite",
          "correct": false,
          "feedback": "Erreur : parcours en profondeur n'organise pas les visites par\nniveaux ; difficile d'extraire « distance $2$ ».\n"
        },
        {
          "text": "Une recherche dichotomique",
          "correct": false,
          "feedback": "Erreur : la dichotomie suppose une structure\nordonnée, ce qui n'est pas le cas d'un graphe\nsocial.\n"
        }
      ],
      "explanation": "Cette technique est utilisée par LinkedIn, Facebook,\netc. La théorie des « six degrés de séparation »\naffirme que dans le graphe social mondial, la\ndistance maximale est environ $6$."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "implementation-dictionnaire"
      ],
      "title": "Liste d'adjacence en Python",
      "statement": "Comment représenter naturellement une liste d'adjacence\nen Python ?",
      "options": [
        {
          "text": "Comme un entier",
          "correct": false,
          "feedback": "Erreur : un entier ne peut pas représenter une\nstructure complexe.\n"
        },
        {
          "text": "Comme un tuple unique",
          "correct": false,
          "feedback": "Erreur : trop limité pour stocker la structure.\n"
        },
        {
          "text": "Comme une chaîne de caractères",
          "correct": false,
          "feedback": "Erreur : difficile à manipuler et inefficace.\n"
        },
        {
          "text": "Comme un dictionnaire où la clé est un sommet et la valeur est la liste de ses voisins",
          "correct": true,
          "feedback": "Bonne réponse : par exemple,\n`g = {'A': ['B', 'C'], 'B': ['A'], 'C': ['A']}`.\nTrès lisible et efficace. On accède aux voisins\nen $O(1)$ via la clé.\n"
        }
      ],
      "explanation": "Variante orientée objet : créer une classe `Graphe`\navec méthodes `ajouter_sommet`, `ajouter_arete`,\n`voisins`, etc. Plus propre pour des projets\nstructurés. Mais en NSI, le dictionnaire suffit\nsouvent."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "parcours-implementation"
      ],
      "title": "Code parcours en largeur",
      "statement": "Quelle fonction Python implémente correctement un parcours en largeur\ndepuis un sommet `depart` dans un graphe `g` (dictionnaire) ?",
      "options": [
        {
          "text": "```\ndef bfs(g, depart):\n    return [v for v in g]\n```\n",
          "correct": false,
          "feedback": "Erreur : retourne tous les sommets sans\nparcours. Ne respecte pas la connexité depuis\n`depart`.\n"
        },
        {
          "text": "```\ndef bfs(g, depart):\n    visites = {depart}\n    from collections import deque\n    f = deque([depart])\n    while f:\n        v = f.popleft()\n        for w in g[v]:\n            if w not in visites:\n                visites.add(w)\n                f.append(w)\n    return visites\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : structure standard. On garde une\nensemble `visites` pour éviter les boucles\ninfinies sur les graphes cycliques. La file\nimpose le parcours en largeur.\n"
        },
        {
          "text": "```\ndef bfs(g, depart):\n    return g[depart]\n```\n",
          "correct": false,
          "feedback": "Erreur : retourne uniquement les voisins\ndirects, pas le parcours complet.\n"
        },
        {
          "text": "```\ndef bfs(g, depart):\n    return sorted(g.keys())\n```\n",
          "correct": false,
          "feedback": "Erreur : trie les clés sans parcourir le graphe.\n"
        }
      ],
      "explanation": "L'ensemble `visites` est crucial : sans lui, sur un\ngraphe avec cycle, on bouclerait indéfiniment.\nC'est l'une des erreurs classiques sur les graphes."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "arbre-vs-graphe"
      ],
      "title": "Arbre comme cas particulier",
      "statement": "Un **arbre** (au sens informatique) est un cas\nparticulier de :",
      "options": [
        {
          "text": "Graphe : c'est un graphe non orienté, connexe et acyclique",
          "correct": true,
          "feedback": "Bonne réponse : tous les concepts d'arbre\n(racine, hauteur, parcours) sont applicables aux\ngraphes plus généralement, mais l'arbre garantit\ndes propriétés (chemin unique entre deux\nsommets) qui simplifient les algorithmes.\n"
        },
        {
          "text": "Tableau",
          "correct": false,
          "feedback": "Erreur : un tableau est linéaire, un arbre\nhiérarchique.\n"
        },
        {
          "text": "File",
          "correct": false,
          "feedback": "Erreur : file et arbre sont distincts.\n"
        },
        {
          "text": "Pile",
          "correct": false,
          "feedback": "Erreur : pile et arbre sont des structures\ntotalement différentes.\n"
        }
      ],
      "explanation": "Tout arbre à $n$ sommets a exactement $n - 1$\narêtes. Si l'on a $n$ sommets, $n - 1$ arêtes et la\nconnexité, alors c'est un arbre (et donc sans\ncycle). Triple caractérisation."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "dijkstra"
      ],
      "title": "Algorithme de Dijkstra",
      "statement": "L'algorithme de **Dijkstra** calcule les plus courts\nchemins depuis une source dans un graphe pondéré. Quelle\nest sa contrainte sur les poids ?",
      "options": [
        {
          "text": "Les poids doivent être positifs ou nuls (pas de poids négatifs)",
          "correct": true,
          "feedback": "Bonne réponse : avec des poids négatifs,\nl'invariant glouton de Dijkstra ne tient plus.\nPour les poids négatifs, utiliser Bellman-Ford.\n"
        },
        {
          "text": "Les poids doivent être entiers",
          "correct": false,
          "feedback": "Erreur : Dijkstra fonctionne avec des poids\nréels.\n"
        },
        {
          "text": "Les poids doivent être triés à l'avance",
          "correct": false,
          "feedback": "Erreur : aucun tri préalable requis. Dijkstra\ngère lui-même la priorité via une file de\npriorité.\n"
        },
        {
          "text": "Les poids doivent être tous identiques",
          "correct": false,
          "feedback": "Erreur : si tous les poids sont identiques, parcours en largeur\nsuffit. Dijkstra est conçu pour des poids\nvariables.\n"
        }
      ],
      "explanation": "Dijkstra s'implémente classiquement avec une **file\nde priorité** (tas), donnant une complexité\n$O((n + m) \\log n)$. Très utilisé dans les GPS,\nles protocoles de routage et les jeux vidéo\n(recherche de chemin pour les déplacements de\npersonnages)."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "tri-topologique"
      ],
      "title": "Tri topologique",
      "statement": "Un **tri topologique** d'un DAG :",
      "options": [
        {
          "text": "Range les sommets dans un ordre tel que pour chaque arête $u \\to v$, $u$ apparaît avant $v$",
          "correct": true,
          "feedback": "Bonne réponse : c'est un ordre **respectant les\ndépendances**. Très utile pour ordonnancer des\ntâches : faire $u$ avant $v$ si $u$ est requis\npour $v$. Plusieurs ordres valides sont possibles\nen général.\n"
        },
        {
          "text": "Trie les sommets par valeur croissante",
          "correct": false,
          "feedback": "Erreur : pas un tri par valeur. C'est un tri\nstructurel basé sur les dépendances.\n"
        },
        {
          "text": "Élimine les cycles",
          "correct": false,
          "feedback": "Erreur : un tri topologique requiert un graphe\n**déjà** acyclique (DAG). On ne supprime pas de\ncycles.\n"
        },
        {
          "text": "Trie les sommets par degré",
          "correct": false,
          "feedback": "Erreur : pas de tri par degré.\n"
        }
      ],
      "explanation": "Algorithmes : algorithme de Kahn (par degré\nentrant) ou parcours en profondeur avec post-ordre inversé. Application\nclassique : compilation, ordonnancement de\nmakefiles, calcul de tableaux d'événements."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "composantes-connexes"
      ],
      "title": "Composantes connexes",
      "statement": "Pour calculer les **composantes connexes** d'un graphe\nnon orienté, on peut :",
      "options": [
        {
          "text": "Utiliser une recherche dichotomique",
          "correct": false,
          "feedback": "Erreur : pas adapté aux graphes non ordonnés.\n"
        },
        {
          "text": "Lancer un parcours en largeur (ou parcours en profondeur) depuis chaque sommet non encore visité ; chaque parcours visite une composante",
          "correct": true,
          "feedback": "Bonne réponse : algorithme classique en $O(n + m)$.\nTant qu'il reste des sommets non visités, on en\nchoisit un et on lance un parcours qui marque\ntous les sommets de sa composante. On compte le\nnombre de parcours lancés.\n"
        },
        {
          "text": "Compter les sommets",
          "correct": false,
          "feedback": "Erreur : ça donne $n$, pas le nombre de\ncomposantes.\n"
        },
        {
          "text": "Trier les sommets par numéro",
          "correct": false,
          "feedback": "Le tri des sommets ne\nrévèle pas leur\nappartenance à une\ncomposante connexe : il\nfaut suivre les arêtes\ndu graphe pour\ndéterminer cette\nappartenance.\n"
        }
      ],
      "explanation": "Cette technique fonctionne aussi avec une\nstructure d'**Union-Find** (très efficace pour\nles graphes statiques). En NSI, le parcours en largeur/parcours en profondeur suffit\nlargement."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "arbre-couvrant"
      ],
      "title": "Arbre couvrant",
      "statement": "Un **arbre couvrant** d'un graphe connexe est :",
      "options": [
        {
          "text": "Un sous-graphe qui contient toutes les arêtes",
          "correct": false,
          "feedback": "Erreur : ce serait le graphe lui-même.\n"
        },
        {
          "text": "Une racine du graphe",
          "correct": false,
          "feedback": "Erreur : un graphe général n'a pas de racine.\n"
        },
        {
          "text": "Un cycle qui passe par tous les sommets",
          "correct": false,
          "feedback": "Erreur : c'est un cycle hamiltonien, pas un\narbre couvrant.\n"
        },
        {
          "text": "Un sous-graphe qui contient tous les sommets et forme un arbre (connexe, sans cycle)",
          "correct": true,
          "feedback": "Bonne réponse : un arbre couvrant contient $n$\nsommets et $n - 1$ arêtes choisies parmi celles\ndu graphe initial. Quand le graphe est pondéré,\non peut chercher l'arbre couvrant **minimal**\n(Kruskal, Prim) : minimise la somme des poids.\n"
        }
      ],
      "explanation": "Application classique de l'arbre couvrant minimal :\nréseaux électriques (minimiser la longueur de\ncâble), réseaux informatiques, plans\nd'urbanisation. Algorithmes : Kruskal (glouton sur\nles arêtes), Prim (glouton sur les sommets)."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "synthese"
      ],
      "title": "Choisir le bon algorithme",
      "statement": "Vous voulez calculer le chemin le plus court entre deux\nvilles sur un réseau routier (les routes ont des\nlongueurs en kilomètres positives). Quel algorithme\nchoisir ?",
      "options": [
        {
          "text": "parcours en profondeur",
          "correct": false,
          "feedback": "Erreur : parcours en profondeur ne calcule pas les plus courts\nchemins.\n"
        },
        {
          "text": "parcours en largeur",
          "correct": false,
          "feedback": "Erreur : parcours en largeur minimise le **nombre d'arêtes**, pas\nla **somme des poids**. Le résultat ignorerait\nles longueurs.\n"
        },
        {
          "text": "Tri topologique",
          "correct": false,
          "feedback": "Erreur : utile pour les DAG, pas pour les\nplus courts chemins en général.\n"
        },
        {
          "text": "L'algorithme de Dijkstra (les poids sont positifs)",
          "correct": true,
          "feedback": "Bonne réponse : Dijkstra est exactement conçu\npour ce cas. Pour des graphes très grands, on\nutilise des variantes optimisées (A*, Dijkstra\nbidirectionnel, contractions).\n"
        }
      ],
      "explanation": "Récapitulatif algorithmes plus courts chemins :\nparcours en largeur (non pondéré), Dijkstra (poids positifs),\nBellman-Ford (poids négatifs sans cycle négatif),\nFloyd-Warshall (toutes paires de sommets en\n$O(n^3)$)."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "conversion-representation"
      ],
      "title": "Conversion entre représentations",
      "statement": "Soit le graphe non orienté représenté par la\nliste d'adjacence\n`g = {'A': ['B', 'C'], 'B': ['A', 'C'], 'C': ['A', 'B']}`.\nQuelle est la matrice d'adjacence correspondante\navec l'ordre des sommets `[A, B, C]` ?",
      "options": [
        {
          "text": "```\n[[0, 1, 0],\n [1, 0, 1],\n [0, 1, 0]]\n```\n",
          "correct": false,
          "feedback": "Erreur : cette matrice correspondrait à un\ngraphe en chaîne `A - B - C`, où `A` et `C`\nne sont pas directement reliés. Or le graphe\ndonné est complet : `A` et `C` sont voisins.\n"
        },
        {
          "text": "```\n[[0, 1, 1],\n [1, 0, 1],\n [1, 1, 0]]\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : chaque sommet est relié à\nchacun des deux autres (graphe complet $K_3$).\nLa diagonale est nulle (pas de boucle), et la\nmatrice est symétrique (graphe non orienté).\nToutes les autres cases valent $1$ car les\narêtes existent dans tous les sens.\n"
        },
        {
          "text": "```\n[[0, 0, 0],\n [0, 0, 0],\n [0, 0, 0]]\n```\n",
          "correct": false,
          "feedback": "Erreur : ce serait un graphe sans aucune\narête. Or le graphe possède bien des arêtes\n(chaque sommet a des voisins dans la liste\nd'adjacence).\n"
        },
        {
          "text": "```\n[[1, 1, 1],\n [1, 1, 1],\n [1, 1, 1]]\n```\n",
          "correct": false,
          "feedback": "Erreur : la diagonale ne doit pas être à $1$\ncar aucun sommet n'a de boucle sur lui-même\n(par exemple, `g['A']` ne contient pas `'A'`).\n"
        }
      ],
      "explanation": "Pour un graphe non orienté, la matrice\nd'adjacence est toujours **symétrique**. Pour un\ngraphe orienté, ce n'est pas nécessairement le\ncas. Les deux représentations sont équivalentes\nen information ; le choix dépend des opérations\nles plus fréquentes (test d'arête en O(1) avec\nla matrice ; énumération des voisins efficace\navec la liste)."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "poignees-mains"
      ],
      "title": "Théorème des poignées de mains",
      "statement": "Dans un graphe **non orienté**, quelle relation\nrelie la somme $S$ des degrés de tous les\nsommets et le nombre $m$ d'arêtes ?",
      "options": [
        {
          "text": "$S = n$ (nombre de sommets)",
          "correct": false,
          "feedback": "Erreur : la somme des degrés dépend du\nnombre d'**arêtes**, pas du nombre de\nsommets. Un graphe avec beaucoup de sommets\nmais peu d'arêtes a une somme des degrés\nfaible.\n"
        },
        {
          "text": "$S = m$",
          "correct": false,
          "feedback": "Erreur : oublie le double comptage. Chaque\narête participe à **deux** degrés (un par\nextrémité), pas un seul.\n"
        },
        {
          "text": "$S = m^2$",
          "correct": false,
          "feedback": "Erreur : aucune raison d'avoir une relation\nquadratique. La relation est linéaire : $S$\ndépend de $m$ par un facteur multiplicatif\nde $2$.\n"
        },
        {
          "text": "$S = 2m$",
          "correct": true,
          "feedback": "Bonne réponse : c'est le théorème des poignées\nde mains. Chaque arête contribue $1$ au degré\nde chacun de ses deux extrémités, donc compte\ndeux fois dans la somme des degrés. D'où\n$S = 2m$. Conséquence directe : la somme des\ndegrés est toujours **paire**, et le nombre\nde sommets de degré impair est aussi **pair**.\n"
        }
      ],
      "explanation": "Conséquence amusante : dans un groupe de\npersonnes, le nombre de personnes ayant serré\nla main à un nombre **impair** d'autres\npersonnes est forcément pair. C'est l'une des\npremières applications de la théorie des\ngraphes (Euler, $1736$)."
    },
    {
      "id": "q28",
      "difficulty": 2,
      "skills": [
        "trace-bfs"
      ],
      "title": "Trace d'un parcours en largeur",
      "statement": "On effectue un parcours en largeur du graphe\n`g = {'A': ['B', 'C'], 'B': ['A', 'D'], 'C': ['A', 'D'], 'D': ['B', 'C', 'E'], 'E': ['D']}`\nen partant de `'A'`, en visitant les voisins\ndans l'ordre où ils apparaissent dans la liste.\nDans quel ordre les sommets sont-ils visités ?",
      "options": [
        {
          "text": "A, B, C, D, E\n",
          "correct": true,
          "feedback": "Bonne réponse : on commence par enfiler `A`,\npuis on défile `A` et on enfile ses voisins\n`B` et `C`. On défile `B`, on enfile `D`\n(`A` est déjà visité). On défile `C` (ses\nvoisins `A` et `D` sont déjà visités ou\nenfilés). On défile `D`, on enfile `E`. On\ndéfile `E`. Ordre de visite : A, B, C, D, E.\n"
        },
        {
          "text": "A, B, D, E, C\n",
          "correct": false,
          "feedback": "Erreur : c'est un parcours en **profondeur**\n(qui suit une branche en suivant les\nvoisins). Le parcours en largeur, lui,\nvisite les sommets par niveaux croissants\nde distance, pas en plongeant.\n"
        },
        {
          "text": "A, C, B, D, E\n",
          "correct": false,
          "feedback": "Erreur : on visite les voisins dans l'ordre\noù ils apparaissent dans la liste. Pour `A`,\nc'est `B` puis `C`. Donc `B` est visité\navant `C`, pas l'inverse.\n"
        },
        {
          "text": "A, B, C, E, D\n",
          "correct": false,
          "feedback": "Erreur : on visite `D` avant `E` car `D` est\nenfilé en premier (par `B`), et `E` n'est\nenfilé que plus tard (quand on visite `D`).\nL'ordre est donc D avant E.\n"
        }
      ],
      "explanation": "Le parcours en largeur visite les sommets dans\nl'ordre croissant de leur distance (en arêtes)\nau sommet de départ. Niveau $0$ : $A$. Niveau\n$1$ : $B$, $C$. Niveau $2$ : $D$. Niveau $3$ :\n$E$. C'est exactement ce qui justifie l'usage\ndu parcours en largeur pour calculer les plus\ncourts chemins en nombre d'arêtes."
    },
    {
      "id": "q29",
      "difficulty": 3,
      "skills": [
        "trace-dijkstra"
      ],
      "title": "Trace de l'algorithme de Dijkstra",
      "statement": "On considère le graphe pondéré suivant\n(arêtes non orientées) :\n`A-B (4)`, `A-C (2)`, `B-C (1)`, `B-D (5)`,\n`C-D (8)`, `C-E (10)`, `D-E (2)`. On lance\nDijkstra depuis le sommet `A`. Quelle est la\ndistance minimale de `A` à `E` ?",
      "options": [
        {
          "text": "$14$ (en passant par `A-B-D-E`)",
          "correct": false,
          "feedback": "Erreur : ce chemin pèse $4 + 5 + 2 = 11$,\npas $14$. Et il n'est pas optimal :\npasser d'abord par `C` permet d'atteindre\n`B` à coût $3$ (au lieu de $4$), ce qui\nréduit ensuite le chemin total.\n"
        },
        {
          "text": "$12$ (en passant directement par `C-E`)",
          "correct": false,
          "feedback": "Erreur : c'est une distance valide\n(chemin `A → C → E`), mais ce n'est pas\nla **plus courte**. Le chemin\n`A → C → B → D → E` totalise\n$2 + 1 + 5 + 2 = 10$, ce qui est moins.\nDijkstra trouve toujours le minimum.\n"
        },
        {
          "text": "$11$",
          "correct": false,
          "feedback": "Erreur : aucun chemin de `A` à `E` n'a\nce poids. Refaire le calcul en\nconsidérant les chemins possibles :\n`A → C → E` pèse $12$, `A → B → D → E`\npèse $4 + 5 + 2 = 11$, et\n`A → C → B → D → E` pèse\n$2 + 1 + 5 + 2 = 10$. C'est ce dernier\nqui est minimal.\n"
        },
        {
          "text": "$10$",
          "correct": true,
          "feedback": "Bonne réponse : on trace les distances\nsuccessivement. Étape 1 : on extrait\n`A` ($0$), on met à jour `B=4`, `C=2`.\nÉtape 2 : on extrait `C` (le plus\npetit, $2$), on met à jour\n`B = min(4, 2+1) = 3`, `D = 2+8 = 10`,\n`E = 2+10 = 12`. Étape 3 : on extrait\n`B` ($3$), on met à jour\n`D = min(10, 3+5) = 8`. Étape 4 : on\nextrait `D` ($8$), on met à jour\n`E = min(12, 8+2) = 10`. Étape 5 : on\nextrait `E` ($10$), parcours terminé.\nLe chemin optimal est donc\n`A → C → B → D → E` de poids total\n$2 + 1 + 5 + 2 = 10$.\n"
        }
      ],
      "explanation": "Méthode systématique : tenir un tableau\n`dist[v]` pour chaque sommet. Tant qu'il\nreste des sommets non traités, extraire le\nsommet de plus petite distance, et relâcher\n(mettre à jour) ses voisins :\n`dist[w] = min(dist[w], dist[v] + poids(v,w))`.\nCet algorithme est **glouton** : on traite\ndéfinitivement un sommet quand on l'extrait.\nCela exige des poids positifs ; sinon,\nutiliser Bellman-Ford."
    },
    {
      "id": "q30",
      "difficulty": 2,
      "skills": [
        "code-dfs-recursif"
      ],
      "title": "Code du parcours en profondeur récursif",
      "statement": "Quel code Python implémente correctement un\nparcours en profondeur récursif d'un graphe `g`\n(dictionnaire d'adjacence) à partir d'un\nsommet `depart` ?",
      "options": [
        {
          "text": "```python\ndef parcours_profondeur(g, depart, visites=None):\n    if visites is None:\n        visites = set()\n    visites.add(depart)\n    for voisin in g[depart]:\n        if voisin not in visites:\n            parcours_profondeur(g, voisin, visites)\n    return visites\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : on marque le sommet\ncourant, puis on appelle récursivement\nla fonction sur chaque voisin non encore\nvisité. La récursivité utilise\nimplicitement la pile d'appels, ce qui\nréalise naturellement le LIFO du\nparcours en profondeur. L'astuce\n`visites=None` puis création d'un\nensemble dans la fonction évite le piège\ndu paramètre par défaut mutable, qui\nserait partagé entre les appels\nsuccessifs.\n"
        },
        {
          "text": "```python\ndef parcours_profondeur(g, depart):\n    for voisin in g[depart]:\n        parcours_profondeur(g, voisin)\n```\n",
          "correct": false,
          "feedback": "Erreur : aucune trace des sommets\nvisités. Sur un graphe avec un cycle, la\nfonction boucle indéfiniment et finit\npar lever `RecursionError`. La\nmémorisation des sommets déjà visités\nest essentielle.\n"
        },
        {
          "text": "```python\ndef parcours_profondeur(g, depart):\n    return list(g[depart])\n```\n",
          "correct": false,
          "feedback": "Erreur : ce code retourne uniquement les\nvoisins directs du sommet de départ,\npas le parcours complet. Il manque la\ndescente récursive dans les voisins.\n"
        },
        {
          "text": "```python\ndef parcours_profondeur(g, depart, visites={}):\n    visites[depart] = True\n    for voisin in g[depart]:\n        if voisin not in visites:\n            parcours_profondeur(g, voisin, visites)\n    return visites\n```\n",
          "correct": false,
          "feedback": "Piège classique du paramètre par défaut\nmutable : le dictionnaire `visites = {}`\nest créé **une seule fois**, à la\ndéfinition de la fonction. Tous les\nappels successifs à\n`parcours_profondeur(g, ...)` partagent\nalors le même dictionnaire, ce qui\nconduit à un bug subtil : un nouveau\nparcours conserve les sommets visités\nd'un parcours précédent.\n"
        }
      ],
      "explanation": "Pour transformer ce parcours en profondeur\nrécursif en parcours en profondeur itératif\navec pile explicite, on remplace la\nrécursion par une pile que l'on dépile et\nempile manuellement. Cela permet de mieux\ncontrôler la consommation de pile et\nd'éviter `RecursionError` sur les très\ngrands graphes."
    },
    {
      "id": "q31",
      "difficulty": 3,
      "skills": [
        "detection-cycle"
      ],
      "title": "Détection de cycle dans un graphe orienté",
      "statement": "Comment **détecter la présence d'un cycle**\ndans un graphe **orienté** à l'aide d'un\nparcours en profondeur ?",
      "options": [
        {
          "text": "On marque chaque sommet avec trois états :\nnon visité, en cours de visite\n(sur la pile d'appels actuelle), et\nterminé. Si pendant le parcours, on\nrencontre une arête vers un sommet « en\ncours de visite », on a détecté un cycle\n",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'algorithme\nstandard, dit des « trois couleurs »\n(blanc, gris, noir). Le sommet « gris »\nest en cours de visite : si on retombe\ndessus, c'est qu'on a fait un cycle.\nSans la distinction « en cours / terminé »,\non confondrait un cycle avec une simple\nréutilisation d'un sommet déjà exploré\ndans une autre branche.\n"
        },
        {
          "text": "On compte simplement le nombre d'arêtes :\ns'il dépasse $n - 1$, il y a un cycle\n",
          "correct": false,
          "feedback": "Erreur : cette propriété est vraie pour\nles graphes **non orientés connexes** (un\narbre couvrant a exactement $n - 1$\narêtes), mais pas pour les graphes\norientés en général. Un graphe orienté\npeut avoir beaucoup plus d'arêtes sans\ncycle (DAG dense).\n"
        },
        {
          "text": "Il est impossible de détecter un cycle en\ntemps polynomial\n",
          "correct": false,
          "feedback": "Erreur : la détection de cycle se fait en\n$O(n + m)$ avec un parcours en\nprofondeur. C'est l'un des algorithmes\nles plus efficaces sur les graphes.\n"
        },
        {
          "text": "On regarde si la matrice d'adjacence est\nsymétrique\n",
          "correct": false,
          "feedback": "Erreur : la symétrie de la matrice\nd'adjacence indique que le graphe est\n**non orienté** (toute arête a son\nretour), pas qu'il y a un cycle.\n"
        }
      ],
      "explanation": "Application directe : un graphe orienté est\nun **DAG** si et seulement s'il ne contient\naucun cycle. La détection de cycles est\ndonc l'algorithme préliminaire à tout tri\ntopologique. Sur un graphe **non orienté**,\nla détection est plus simple : il suffit de\nvérifier qu'au cours du parcours en\nprofondeur, on ne rencontre pas un voisin\ndéjà visité (autre que le parent immédiat)."
    },
    {
      "id": "q32",
      "difficulty": 3,
      "skills": [
        "tri-topologique-kahn"
      ],
      "title": "Algorithme de Kahn pour le tri topologique",
      "statement": "Quel est le principe de l'**algorithme de\nKahn** pour calculer un tri topologique d'un\nDAG ?",
      "options": [
        {
          "text": "On effectue un parcours en largeur depuis\nle premier sommet\n",
          "correct": false,
          "feedback": "Erreur : le parcours en largeur\nstandard parcourt par distance, pas par\ndépendances. Il existe une variante du\nparcours en profondeur (post-ordre\ninversé) qui donne aussi un tri\ntopologique, mais ce n'est pas l'idée\nde Kahn.\n"
        },
        {
          "text": "On trie les sommets par ordre alphabétique\nde leur identifiant\n",
          "correct": false,
          "feedback": "Erreur : un tri alphabétique ne respecte\npas les dépendances. Le tri topologique\ndoit garantir que pour chaque arête\n`u → v`, `u` apparaît avant `v` dans\nl'ordre, ce que l'ordre alphabétique ne\nfait absolument pas en général.\n"
        },
        {
          "text": "On retire répétitivement les sommets de\ndegré entrant nul (qui ne dépendent\nde rien) ; à chaque retrait, on les\najoute à la liste de sortie et on\ndécrémente le degré entrant de leurs\nvoisins. On continue jusqu'à ce qu'il ne\nreste plus aucun sommet\n",
          "correct": true,
          "feedback": "Bonne réponse : c'est un algorithme\nglouton très intuitif. À chaque étape,\non traite un sommet « libre de toute\ndépendance ». Si à un moment plus aucun\nsommet n'est de degré entrant nul mais\nqu'il en reste, c'est qu'il y a un\ncycle (le graphe n'est pas un DAG).\nImplémentation : maintenir une file (ou\nensemble) des sommets de degré entrant\nnul, et la mettre à jour à chaque retrait.\nComplexité : $O(n + m)$.\n"
        },
        {
          "text": "On calcule la matrice d'adjacence puis on\ncherche les valeurs propres\n",
          "correct": false,
          "feedback": "Erreur : aucun rapport. Le calcul de\nvaleurs propres est une opération\nd'algèbre linéaire, sans lien avec le\ntri topologique des graphes.\n"
        }
      ],
      "explanation": "Application : compilation d'un projet (les\nmodules dépendent les uns des autres),\nordonnancement de tâches (certaines\ndoivent précéder d'autres), résolution de\nformules dans un tableur (l'ordre de\nrecalcul respecte les dépendances entre\ncellules). Si le graphe contient un cycle,\nl'algorithme de Kahn s'arrête prématurément\net révèle l'impossibilité d'un tri\ntopologique."
    }
  ]
}