{
  "chapter": {
    "id": "recherche-dichotomique",
    "level": "premiere",
    "theme": "Algorithmique",
    "title": "Recherche dichotomique",
    "description": "Recherche dichotomique dans une liste triée, principe\nd'élimination de la moitié, calcul du milieu, indices\ngauche/droite, coût logarithmique, implémentation\nitérative, terminaison à l'aide d'un variant de boucle,\npièges classiques (bornes, doublons).",
    "prerequisites": [],
    "references": []
  },
  "questions": [
    {
      "id": "q01",
      "difficulty": 1,
      "skills": [
        "definition"
      ],
      "title": "Principe",
      "statement": "Quel est le principe de la **recherche dichotomique** ?",
      "options": [
        {
          "text": "Trier la liste à nouveau avant chaque nouvelle comparaison",
          "correct": false,
          "feedback": "Erreur : la liste doit être triée **avant**\nla recherche, pas pendant.\n"
        },
        {
          "text": "Utiliser un dictionnaire pour accéder directement à la cible",
          "correct": false,
          "feedback": "Erreur : c'est une autre stratégie (table\nde hachage), pas la dichotomie.\n"
        },
        {
          "text": "Parcourir la liste élément par élément depuis le début",
          "correct": false,
          "feedback": "Erreur : c'est la recherche **linéaire**.\n"
        },
        {
          "text": "Comparer la cible au milieu et ne garder qu'une moitié",
          "correct": true,
          "feedback": "Bonne réponse : à chaque étape, on **élimine la moitié** de la liste, ce qui donne une\ncomplexité logarithmique.\n"
        }
      ],
      "explanation": "« Dichotomie » vient du grec « di » (deux) +\n« tomos » (couper) : couper en deux. À chaque\nétape, on coupe l'intervalle de recherche par\ndeux."
    },
    {
      "id": "q02",
      "difficulty": 1,
      "skills": [
        "precondition"
      ],
      "title": "Précondition",
      "statement": "La recherche dichotomique exige-t-elle une\ncondition particulière sur la liste ?",
      "options": [
        {
          "text": "La liste doit être de taille au moins 100",
          "correct": false,
          "feedback": "Erreur : aucune condition de taille minimale.\n"
        },
        {
          "text": "La liste doit être de longueur paire",
          "correct": false,
          "feedback": "Erreur : la dichotomie fonctionne sur\nn'importe quelle longueur.\n"
        },
        {
          "text": "La liste doit contenir uniquement des entiers",
          "correct": false,
          "feedback": "Erreur : la dichotomie fonctionne sur tout\ntype comparable (entiers, flottants, chaînes).\n"
        },
        {
          "text": "La liste doit être triée",
          "correct": true,
          "feedback": "Bonne réponse : c'est la condition\nessentielle. Sans tri, la comparaison au\nmilieu ne permet pas de savoir dans quelle\nmoitié chercher.\n"
        }
      ],
      "explanation": "Si la liste n'est pas triée, on ne peut pas\nconclure qu'un élément cherché se trouve « avant »\nou « après » le milieu. C'est l'hypothèse-clé."
    },
    {
      "id": "q03",
      "difficulty": 1,
      "skills": [
        "complexite"
      ],
      "title": "Complexité",
      "statement": "Quelle est la **complexité dans le pire cas** de\nla recherche dichotomique ?",
      "options": [
        {
          "text": "O(1)",
          "correct": false,
          "feedback": "Erreur : on doit faire plusieurs comparaisons\ndans le pire cas.\n"
        },
        {
          "text": "O(n log n)",
          "correct": false,
          "feedback": "Erreur : c'est la complexité du tri (par\nfusion ou rapide), pas de la recherche.\n"
        },
        {
          "text": "O(log n)",
          "correct": true,
          "feedback": "Bonne réponse : à chaque étape on divise\nl'intervalle par 2, donc le nombre d'étapes\nest `log₂(n)`.\n"
        },
        {
          "text": "O(n)",
          "correct": false,
          "feedback": "Erreur : c'est la complexité de la recherche\n**linéaire**.\n"
        }
      ],
      "explanation": "Pour 10⁶ éléments : ~ 20 comparaisons. Pour 10⁹\néléments : ~ 30 comparaisons. La dichotomie\npasse à l'échelle remarquablement."
    },
    {
      "id": "q04",
      "difficulty": 1,
      "skills": [
        "milieu"
      ],
      "title": "Calcul du milieu",
      "statement": "Avec deux indices `g` (gauche) et `d` (droite),\ncomment calcule-t-on l'**indice du milieu** en\nPython ?",
      "options": [
        {
          "text": "`m = (g + d) // 2`",
          "correct": true,
          "feedback": "Bonne réponse : moyenne entière des deux\nindices. Le `//` (division entière) garantit\nun indice valide.\n"
        },
        {
          "text": "`m = g - d`",
          "correct": false,
          "feedback": "Erreur : différence, pas milieu.\n"
        },
        {
          "text": "`m = g + d`",
          "correct": false,
          "feedback": "Erreur : ce n'est pas le milieu, c'est la\nsomme.\n"
        },
        {
          "text": "`m = (g + d) / 2`",
          "correct": false,
          "feedback": "Erreur : `/` produit un **flottant** en\nPython 3, qui ne peut pas servir d'indice.\n"
        }
      ],
      "explanation": "Variante équivalente :\n`m = g + (d - g) // 2`. C'est plus robuste sur\ncertains langages où `g + d` peut déborder, mais\nen Python (entiers de précision arbitraire),\n`(g + d) // 2` suffit."
    },
    {
      "id": "q05",
      "difficulty": 1,
      "skills": [
        "comparaison"
      ],
      "title": "Élimination de moitié",
      "statement": "Si la valeur à l'indice du milieu est **plus petite** que la cible (et la liste est triée\ncroissant), où chercher ensuite ?",
      "options": [
        {
          "text": "Dans la moitié droite",
          "correct": true,
          "feedback": "Bonne réponse : si le milieu est plus petit\nque la cible, alors toutes les valeurs à\ngauche le sont aussi (liste triée). On peut\nles éliminer.\n"
        },
        {
          "text": "Dans la moitié gauche",
          "correct": false,
          "feedback": "Erreur : à gauche, les valeurs sont **encore plus petites** (liste triée croissant).\n"
        },
        {
          "text": "Dans l'ensemble de la liste",
          "correct": false,
          "feedback": "Erreur : l'intérêt de la dichotomie est\nprécisément d'**éliminer** la moitié.\n"
        },
        {
          "text": "Au milieu, à nouveau",
          "correct": false,
          "feedback": "Erreur : on resterait sur la même valeur\nindéfiniment (boucle infinie).\n"
        }
      ],
      "explanation": "Dans le code itératif, on fait `g = m + 1` (et\nnon `g = m`, sinon boucle infinie possible)."
    },
    {
      "id": "q06",
      "difficulty": 1,
      "skills": [
        "code-iteratif"
      ],
      "title": "Code itératif",
      "statement": "Quel code implémente correctement la recherche\ndichotomique itérative (renvoie l'indice ou -1) ?",
      "options": [
        {
          "text": "```python\ndef dicho(liste, cible):\n    g, d = 0, len(liste)\n    while g < d:\n        m = (g + d) // 2\n        if liste[m] == cible:\n            return m\n        g = m\n        d = m\n    return -1\n```\n",
          "correct": false,
          "feedback": "Erreur : `g = m` puis `d = m` vide\nl'intervalle dès la première itération\n(`g == d`, la boucle s'arrête) : la\nfonction renvoie -1 même quand la cible\nest présente, sauf si elle est exactement\nau milieu.\n"
        },
        {
          "text": "```python\ndef dicho(liste, cible):\n    i = 0\n    while i < len(liste):\n        if liste[i] == cible:\n            return i\n        i = i + 1\n    return -1\n```\n",
          "correct": false,
          "feedback": "Erreur : c'est une recherche **linéaire**\ndéguisée : on parcourt les indices un par\nun depuis le début, sans jamais couper\nl'intervalle en deux. Coût O(n) au lieu\nde O(log n).\n"
        },
        {
          "text": "```python\ndef dicho(liste, cible):\n    g, d = 0, len(liste) - 1\n    while g <= d:\n        m = (g + d) // 2\n        if liste[m] == cible:\n            return m\n        elif liste[m] < cible:\n            g = m + 1\n        else:\n            d = m - 1\n    return -1\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : structure classique avec deux\nindices, mise à jour `m + 1` ou `m - 1` pour\néviter les boucles infinies, retour `-1` si\nla cible est absente.\n"
        },
        {
          "text": "```python\ndef dicho(liste, cible):\n    if len(liste) == 0:\n        return -1\n    return liste.index(cible)\n```\n",
          "correct": false,
          "feedback": "Erreur : `list.index` fait une recherche\n**linéaire** en interne, et lève une\nexception `ValueError` si la cible est\nabsente d'une liste non vide.\n"
        }
      ],
      "explanation": "Trois mises à jour d'indices selon la\ncomparaison : égal (trouvé), inférieur (chercher\nà droite), supérieur (chercher à gauche)."
    },
    {
      "id": "q07",
      "difficulty": 1,
      "skills": [
        "borne"
      ],
      "title": "Sortie de boucle",
      "statement": "Dans le code itératif `while g <= d:`, quand la\nboucle se termine-t-elle si la cible est absente ?",
      "options": [
        {
          "text": "Exactement quand les deux indices `g` et `d` deviennent égaux",
          "correct": false,
          "feedback": "Erreur : `g == d` satisfait encore `g <= d`,\nla boucle continue.\n"
        },
        {
          "text": "Au bout de 10 itérations",
          "correct": false,
          "feedback": "Erreur : aucune limite fixe d'itérations.\n"
        },
        {
          "text": "Quand `g > d` (indices croisés)",
          "correct": true,
          "feedback": "Bonne réponse : à chaque échec, l'intervalle\nrétrécit. Quand il devient vide (`g > d`), on\nsort et on renvoie -1.\n"
        },
        {
          "text": "Quand on rencontre une exception",
          "correct": false,
          "feedback": "Erreur : un code correct ne lève pas\nd'exception.\n"
        }
      ],
      "explanation": "C'est la condition d'arrêt « propre » : tant\nqu'il reste au moins un élément candidat\n(`g <= d`), on continue ; sinon, élément absent."
    },
    {
      "id": "q08",
      "difficulty": 1,
      "skills": [
        "vide"
      ],
      "title": "Liste vide",
      "statement": "Que renvoie la dichotomie sur une **liste vide** ?",
      "options": [
        {
          "text": "`None`",
          "correct": false,
          "feedback": "Selon notre convention, on renvoie `-1` (pas\n`None`).\n"
        },
        {
          "text": "Une exception",
          "correct": false,
          "feedback": "Erreur : le code correct gère ce cas\nproprement.\n"
        },
        {
          "text": "`-1`",
          "correct": true,
          "feedback": "Bonne réponse : `g = 0`, `d = -1`, donc\n`g > d` immédiatement. La boucle ne s'exécute\npas et la fonction renvoie `-1` directement.\n"
        },
        {
          "text": "`0`",
          "correct": false,
          "feedback": "Erreur : aucune position 0 dans une liste\nvide.\n"
        }
      ],
      "explanation": "Cas limite à toujours tester. Bonne pratique :\n`assert dicho([], 5) == -1`."
    },
    {
      "id": "q09",
      "difficulty": 1,
      "skills": [
        "comparaison-lineaire"
      ],
      "title": "Pourquoi dichotomique ?",
      "statement": "Quel est le principal **avantage** de la\ndichotomique sur la linéaire ?",
      "options": [
        {
          "text": "Elle est plus simple à programmer",
          "correct": false,
          "feedback": "Au contraire, elle est légèrement plus\ncomplexe (gestion des indices).\n"
        },
        {
          "text": "Elle utilise globalement beaucoup moins de mémoire vive",
          "correct": false,
          "feedback": "Pas un avantage notable : les deux utilisent\npeu de mémoire.\n"
        },
        {
          "text": "Elle ne nécessite pas que la liste soit triée au préalable",
          "correct": false,
          "feedback": "Erreur : c'est la **précondition**\nfondamentale.\n"
        },
        {
          "text": "Elle est bien plus rapide sur les grandes listes triées",
          "correct": true,
          "feedback": "Bonne réponse : passer de O(n) à O(log n)\nest un gain énorme. 1 milliard d'éléments :\n1 milliard de comparaisons en linéaire vs\n~ 30 en dichotomique.\n"
        }
      ],
      "explanation": "Mais attention : si la liste n'est pas triée et\nqu'on la trie juste pour la dichotomie, le tri\ncoûte O(n log n). Pour une **seule** recherche,\nla linéaire est plus rapide."
    },
    {
      "id": "q10",
      "difficulty": 1,
      "skills": [
        "trace"
      ],
      "title": "Trace simple",
      "statement": "On cherche `cible = 17` dans\n`liste = [3, 7, 11, 17, 25, 31, 42]`. Quel est\nl'indice du milieu à la **première étape** ?",
      "options": [
        {
          "text": "4",
          "correct": false,
          "feedback": "Erreur : `(0 + 6) // 2 = 3`, pas 4.\n"
        },
        {
          "text": "6",
          "correct": false,
          "feedback": "Erreur : 6 est l'indice droit, pas le\nmilieu.\n"
        },
        {
          "text": "3",
          "correct": true,
          "feedback": "Bonne réponse : `g = 0`, `d = 6`, milieu =\n`(0 + 6) // 2 = 3`. La valeur en indice 3 est\n`17`, qui est la cible : la dichotomie\nréussit en **une seule** comparaison.\n"
        },
        {
          "text": "0",
          "correct": false,
          "feedback": "Erreur : 0 est l'indice gauche, pas le\nmilieu.\n"
        }
      ],
      "explanation": "Ici, la dichotomie est miraculeusement efficace\nparce que la cible se trouve juste au milieu.\nCas favorable : O(1)."
    },
    {
      "id": "q11",
      "difficulty": 2,
      "skills": [
        "invariant"
      ],
      "title": "Invariant de boucle",
      "statement": "Quel **invariant de boucle** garantit la\ncorrection de la recherche dichotomique\nitérative (avec les indices `g` et `d`) ?",
      "options": [
        {
          "text": "La cible cherchée est toujours placée à l'indice du milieu",
          "correct": false,
          "feedback": "Erreur : sinon la dichotomie réussirait\nen une seule comparaison.\n"
        },
        {
          "text": "La liste reste constamment triée pendant toute la recherche",
          "correct": false,
          "feedback": "Erreur : ce n'est pas un invariant de la\nboucle, c'est une **précondition** sur\nl'entrée.\n"
        },
        {
          "text": "Si la cible est présente, elle est dans `liste[g..d]`",
          "correct": true,
          "feedback": "Bonne réponse : c'est l'invariant clé. À\nl'entrée de chaque itération, la cible\n(si elle existe) est forcément dans la\ntranche encore considérée. Si l'on\nn'élimine que la moitié sans la cible,\nl'invariant reste vrai à la sortie.\n"
        },
        {
          "text": "Les indices `g` et `d` restent égaux à chaque itération",
          "correct": false,
          "feedback": "Erreur : ils ne deviennent égaux qu'à la\ndernière itération (au plus tard).\n"
        }
      ],
      "explanation": "Invariant + condition d'arrêt → correction.\nÀ la sortie de la boucle, soit on a trouvé\nla cible, soit `g > d` (intervalle vide,\ndonc la cible n'est pas dans la liste)."
    },
    {
      "id": "q12",
      "difficulty": 2,
      "skills": [
        "doublons"
      ],
      "title": "Doublons",
      "statement": "Si la cible apparaît **plusieurs fois** dans la\nliste triée, quelle position renvoie la\ndichotomie standard ?",
      "options": [
        {
          "text": "L'indice d'une occurrence quelconque, sans garantie",
          "correct": true,
          "feedback": "Bonne réponse : la dichotomie standard ne\nprécise pas laquelle. Pour avoir la\n**première** ou la **dernière** occurrence,\nil faut une variante (continuer la\ndichotomie même après avoir trouvé).\n"
        },
        {
          "text": "La toute dernière occurrence, située le plus à droite",
          "correct": false,
          "feedback": "Possible mais non garanti.\n"
        },
        {
          "text": "Les indices de toutes les occurrences présentes",
          "correct": false,
          "feedback": "Erreur : la dichotomie renvoie un seul\nindice.\n"
        },
        {
          "text": "Toujours la première occurrence, la plus à gauche",
          "correct": false,
          "feedback": "Possible mais non garanti : dépend du\nparcours.\n"
        }
      ],
      "explanation": "Pour obtenir spécifiquement la **première**\nou la **dernière** occurrence en cas de\ndoublons, on adapte légèrement le code de\nla dichotomie (en continuant à chercher à\ngauche ou à droite après une égalité, au\nlieu de s'arrêter)."
    },
    {
      "id": "q13",
      "difficulty": 2,
      "skills": [
        "trace-detaillee"
      ],
      "title": "Trace détaillée",
      "statement": "On cherche `cible = 25` dans\n`liste = [3, 7, 11, 17, 25, 31, 42]`. Combien\nd'itérations de la boucle la dichotomie itérative\neffectue-t-elle avant de trouver la cible ?",
      "options": [
        {
          "text": "1",
          "correct": false,
          "feedback": "Erreur : une seule itération ne suffit pas. Après\nle premier calcul du milieu (indice 3, valeur 17),\nla cible n'est pas encore localisée.\n"
        },
        {
          "text": "7",
          "correct": false,
          "feedback": "Erreur : c'est le nombre d'éléments de la liste,\npas le nombre d'itérations.\n"
        },
        {
          "text": "2",
          "correct": false,
          "feedback": "Erreur : après deux itérations, l'intervalle s'est\nréduit à l'indice 4, mais la valeur qui s'y trouve\nn'a pas encore été comparée à la cible. Une\ntroisième itération reste nécessaire.\n"
        },
        {
          "text": "3",
          "correct": true,
          "feedback": "Bonne réponse : itération 1, milieu = indice 3\n(valeur 17 < 25, on va à droite, g = 4) ;\nitération 2, milieu = (4 + 6) // 2 = 5 (valeur\n31 > 25, on va à gauche, d = 4) ; itération 3,\nmilieu = 4 (valeur 25 = cible). Soit trois\nitérations de la boucle.\n"
        }
      ],
      "explanation": "Pour log₂(7) ≈ 2,8, on attend au plus 3 itérations\ndans le pire cas. C'est cohérent."
    },
    {
      "id": "q14",
      "difficulty": 2,
      "skills": [
        "absent"
      ],
      "title": "Élément absent",
      "statement": "On cherche `cible = 20` dans\n`liste = [3, 7, 11, 17, 25, 31, 42]`. Que\nrenvoie la dichotomie ?",
      "options": [
        {
          "text": "Une exception",
          "correct": false,
          "feedback": "Erreur : code correct gère le cas absent.\n"
        },
        {
          "text": "L'indice 4 (position où 20 devrait être inséré)",
          "correct": false,
          "feedback": "Erreur : ce serait le rôle d'une variante\n(« position d'insertion ») qui demande une\nadaptation du code. La dichotomie standard\nrenvoie `-1` quand l'élément est absent.\n"
        },
        {
          "text": "La valeur 17 (la plus proche)",
          "correct": false,
          "feedback": "Erreur : la dichotomie ne renvoie pas la\n« valeur la plus proche » par défaut.\n"
        },
        {
          "text": "-1 (élément absent)",
          "correct": true,
          "feedback": "Bonne réponse : 20 n'est pas dans la liste.\nLa boucle se termine avec `g > d` et\nrenvoie `-1`.\n"
        }
      ],
      "explanation": "Pour trouver « la valeur la plus proche » ou\n« la position d'insertion », il faut adapter\nlégèrement le code de la dichotomie : au\nmoment où l'on sortirait avec `-1`, on\nrenvoie plutôt l'indice où la cible aurait\ndû s'insérer."
    },
    {
      "id": "q15",
      "difficulty": 2,
      "skills": [
        "bug-bornes"
      ],
      "title": "Bug de bornes",
      "statement": "Un élève écrit `while g < d:` au lieu de\n`while g <= d:`. Quel est l'effet ?",
      "options": [
        {
          "text": "La fonction devient plus rapide sans changer le résultat",
          "correct": false,
          "feedback": "Erreur : le gain d'une itération se paie\nd'un résultat incorrect sur certaines\nentrées.\n"
        },
        {
          "text": "La fonction finit toujours par renvoyer la valeur -1",
          "correct": false,
          "feedback": "Erreur : elle peut renvoyer un indice si la\ncible est trouvée avant le rétrécissement\nfinal.\n"
        },
        {
          "text": "La fonction sort sans tester le cas `g == d`",
          "correct": true,
          "feedback": "Bonne réponse : si l'intervalle se réduit à\nun seul élément (`g == d`), la condition\n`g < d` est fausse et on sort sans tester\nce dernier élément. Bug subtil mais courant.\n"
        },
        {
          "text": "La boucle devient infinie sur certaines entrées",
          "correct": false,
          "feedback": "Pas de boucle infinie ici (l'intervalle\nrétrécit toujours), mais un bug logique.\n"
        }
      ],
      "explanation": "Toujours tester les **cas limites** : liste à\nun seul élément, cible aux extrémités, cible\nnon présente. Ces tests révèlent les bugs de\nbornes."
    },
    {
      "id": "q16",
      "difficulty": 2,
      "skills": [
        "recherche-trouvee"
      ],
      "title": "Indice retourné en cas de succès",
      "statement": "Une dichotomie standard sur la liste\n`[3, 7, 11, 17, 25, 31, 42]` cherche `cible = 17`.\nQue renvoie-t-elle ?",
      "options": [
        {
          "text": "L'indice `3`",
          "correct": true,
          "feedback": "Bonne réponse : la valeur `17` se trouve\nen position `3` dans la liste (positions\n`0` à `6`). Une dichotomie standard\nrenvoie cet indice quand la cible est\nprésente.\n"
        },
        {
          "text": "La longueur de la liste, c'est-à-dire `7`",
          "correct": false,
          "feedback": "Erreur : la longueur de la liste n'a aucun\nrapport avec la position de la cible. On\nrenvoie `-1` (par convention) seulement\nsi la cible est absente.\n"
        },
        {
          "text": "La position `0` (la première de la liste)",
          "correct": false,
          "feedback": "Erreur : la position `0` correspond à la\nvaleur `3`, pas à `17`. La dichotomie\nrenvoie l'indice exact de la cible\ntrouvée.\n"
        },
        {
          "text": "La valeur `17`",
          "correct": false,
          "feedback": "Erreur : la fonction renvoie l'**indice**\nde la cible, pas sa valeur (qu'on connaît\ndéjà puisqu'on la passe en paramètre).\n"
        }
      ],
      "explanation": "Convention courante : la fonction renvoie\nl'**indice** de la cible quand elle est\ntrouvée, et `-1` (ou parfois `None`) quand\nelle est absente. La méthode `list.index`\nde Python lève une exception `ValueError` à\nla place du `-1`."
    },
    {
      "id": "q17",
      "difficulty": 2,
      "skills": [
        "longueur-1"
      ],
      "title": "Liste à un élément",
      "statement": "Sur `liste = [42]` et `cible = 42`, combien\nd'itérations effectue la dichotomie standard ?",
      "options": [
        {
          "text": "1 (une seule itération suffit)",
          "correct": true,
          "feedback": "Bonne réponse : `g = 0`, `d = 0`, donc\n`g <= d`. Une itération calcule `m = 0`,\ncompare `liste[0]` et trouve la cible.\n"
        },
        {
          "text": "0 (la cible est trouvée sans entrer dans la boucle)",
          "correct": false,
          "feedback": "Erreur : on entre bien dans la boucle pour\ntester la valeur du milieu.\n"
        },
        {
          "text": "2",
          "correct": false,
          "feedback": "Erreur : une itération suffit.\n"
        },
        {
          "text": "Boucle infinie",
          "correct": false,
          "feedback": "Erreur : code correct se termine.\n"
        }
      ],
      "explanation": "Cas limite à tester. Si la cible n'était pas 42\nmais autre chose, il y aurait aussi exactement\nune itération : le milieu est comparé, puis\nl'intervalle devient vide (`g > d`) et la boucle\ns'arrête en renvoyant -1."
    },
    {
      "id": "q18",
      "difficulty": 2,
      "skills": [
        "tri-prealable"
      ],
      "title": "Tri préalable utile ?",
      "statement": "On a une liste **non triée** de 1000 éléments\nsur laquelle on doit faire **une seule**\nrecherche. Faut-il la trier avant pour faire de\nla dichotomie ?",
      "options": [
        {
          "text": "Oui, parce que la dichotomie est toujours préférable",
          "correct": false,
          "feedback": "Erreur : aucune méthode n'est « toujours »\npréférable, tout dépend du contexte. Le\ncoût du tri préalable reste à amortir.\n"
        },
        {
          "text": "Oui, ce serait nettement plus rapide ainsi",
          "correct": false,
          "feedback": "Erreur : trier coûte O(n log n) ≈ 10 000\nopérations, plus 10 pour la dichotomie. La\nrecherche linéaire ne fait au plus que\n1 000 comparaisons. Pas rentable.\n"
        },
        {
          "text": "La dichotomie fonctionne sur une liste non triée",
          "correct": false,
          "feedback": "Erreur : la précondition est essentielle.\n"
        },
        {
          "text": "Non, la linéaire suffit pour une recherche unique",
          "correct": true,
          "feedback": "Bonne réponse : trier juste pour une\nrecherche est contre-productif. La\ndichotomie devient rentable seulement à\npartir de **plusieurs recherches** sur la\nmême liste.\n"
        }
      ],
      "explanation": "Règle : tri rentable si l'on fait au moins ~ k\nrecherches, avec k tel que `n log n + k log n\n< k n`. Approximativement k > log n."
    },
    {
      "id": "q19",
      "difficulty": 2,
      "skills": [
        "pseudo-code"
      ],
      "title": "Recherche par approximation",
      "statement": "Pour trouver la **plus petite valeur ≥ cible**\ndans une liste triée, peut-on utiliser une\ndichotomie ?",
      "options": [
        {
          "text": "Oui, mais cela double la complexité",
          "correct": false,
          "feedback": "Erreur : la complexité reste O(log n).\n"
        },
        {
          "text": "Non, la dichotomie ne fonctionne que pour des comparaisons d'égalité",
          "correct": false,
          "feedback": "Erreur : la dichotomie repose sur des\n**comparaisons d'ordre**, pas d'égalité.\n"
        },
        {
          "text": "Non, cela demande une recherche linéaire",
          "correct": false,
          "feedback": "Erreur : la dichotomie peut être adaptée.\n"
        },
        {
          "text": "Oui, en adaptant légèrement le code de la dichotomie",
          "correct": true,
          "feedback": "Bonne réponse : quand `liste[m] >= cible`,\non mémorise `liste[m]` comme meilleur\ncandidat puis on continue à chercher dans\nla moitié gauche (au lieu de s'arrêter) ;\nsinon on cherche à droite. Le dernier\ncandidat mémorisé est la plus petite valeur\n≥ cible. La complexité reste `O(log n)`.\n"
        }
      ],
      "explanation": "C'est l'un des grands intérêts de la\ndichotomie : elle s'adapte à toutes sortes de\nrequêtes (≤, <, ≥, >) sur des données triées."
    },
    {
      "id": "q20",
      "difficulty": 2,
      "skills": [
        "terminaison"
      ],
      "title": "Terminaison",
      "statement": "Pourquoi la dichotomie itérative **se termine**\ntoujours (pas de boucle infinie) ?",
      "options": [
        {
          "text": "Parce que l'intervalle rétrécit strictement à chaque tour",
          "correct": true,
          "feedback": "Bonne réponse : `g = m + 1` ou `d = m - 1`\nréduit la taille de l'intervalle d'au moins\nun. L'intervalle ne peut donc rétrécir\nindéfiniment, il finit par devenir vide\n(`g > d`).\n"
        },
        {
          "text": "Parce que Python limite les boucles à 1000 itérations",
          "correct": false,
          "feedback": "Erreur : Python n'impose aucune telle limite.\n"
        },
        {
          "text": "Parce que la cible finit forcément par être trouvée",
          "correct": false,
          "feedback": "Erreur : la cible peut être absente, et la\ndichotomie se termine quand même.\n"
        },
        {
          "text": "Parce que la liste est entièrement triée au départ de la recherche",
          "correct": false,
          "feedback": "Erreur : le tri permet la **correction**, pas\nla terminaison.\n"
        }
      ],
      "explanation": "Argument de terminaison : on définit un\n« variant » qui décroît strictement (ici,\n`d - g`). Si le variant est borné inférieurement\n(ici, par 0 ou -1), la boucle se termine."
    },
    {
      "id": "q21",
      "difficulty": 3,
      "skills": [
        "bug-borne-infinie"
      ],
      "title": "Boucle infinie",
      "statement": "Un élève écrit `g = m` (au lieu de `g = m + 1`)\naprès une comparaison `liste[m] < cible`. Que se\npasse-t-il ?",
      "options": [
        {
          "text": "La fonction devient un peu plus rapide, le résultat reste correct",
          "correct": false,
          "feedback": "Erreur : non seulement elle n'est pas plus\nrapide, mais elle peut boucler indéfiniment.\n"
        },
        {
          "text": "La fonction lève une exception `IndexError` au bout de quelques tours",
          "correct": false,
          "feedback": "Erreur : aucune exception, les indices restent\ndans les bornes ; la boucle tourne simplement\nsans fin.\n"
        },
        {
          "text": "La fonction renvoie toujours -1, même si la cible est présente",
          "correct": false,
          "feedback": "Pas systématiquement : selon l'entrée, soit\nelle trouve la cible, soit elle boucle.\n"
        },
        {
          "text": "Une boucle infinie est possible quand il ne reste que deux éléments",
          "correct": true,
          "feedback": "Bonne réponse : quand `d == g + 1`, on a\n`m = (g + d) // 2 = g`. Par exemple avec\n`g = 0`, `d = 1` : `m = 0`, et `g = m` laisse\n`g` à `0` ; la boucle ne progresse plus. D'où\nl'importance d'écrire `g = m + 1`.\n"
        }
      ],
      "explanation": "Bug très classique. Test à faire :\n`dicho([1, 2], 2)` qui doit renvoyer 1. Avec le\nbug, boucle infinie."
    },
    {
      "id": "q22",
      "difficulty": 3,
      "skills": [
        "trace-pas-a-pas"
      ],
      "title": "Trace pas à pas",
      "statement": "On cherche `cible = 2` dans\n`liste = [1, 2, 3, 4, 5, 6, 7, 8]` (n=8). Quels\nsont les indices `m` successivement testés par\nla dichotomie itérative ?",
      "options": [
        {
          "text": "3, 1, 2",
          "correct": false,
          "feedback": "Erreur : la fonction renvoie dès qu'elle\ntrouve la cible (à `m=1`). Elle ne teste pas\nensuite l'indice 2.\n"
        },
        {
          "text": "4 puis 2",
          "correct": false,
          "feedback": "Erreur : `(0+7)//2 = 3`, pas 4. Le calcul\ndu milieu utilise la division entière.\n"
        },
        {
          "text": "3 puis 1",
          "correct": true,
          "feedback": "Bonne réponse : étape 1, `g=0,d=7,m=3`\n(`liste[3]=4 > 2`, on va à gauche : `d=2`) ;\nétape 2, `g=0,d=2,m=1` (`liste[1]=2 = cible`,\nretour immédiat de l'indice 1). Deux indices\ntestés.\n"
        },
        {
          "text": "0, 1, 2 (parcours du début)",
          "correct": false,
          "feedback": "Erreur : ce serait une recherche linéaire.\nLa dichotomie commence par le milieu.\n"
        }
      ],
      "explanation": "Méthode : noter `g`, `d`, `m` à chaque étape,\nmettre à jour les bornes selon la comparaison,\ns'arrêter dès qu'on trouve la cible."
    },
    {
      "id": "q23",
      "difficulty": 3,
      "skills": [
        "variant-loop"
      ],
      "title": "Variant de boucle",
      "statement": "Quel est le **variant de boucle** (quantité qui\ndécroît strictement) de la dichotomie ?",
      "options": [
        {
          "text": "La valeur `m = (g + d) // 2` de l'indice du milieu de la liste",
          "correct": false,
          "feedback": "Erreur : `m` peut augmenter ou diminuer selon\nla comparaison ; il ne décroît pas strictement.\n"
        },
        {
          "text": "La valeur de la cible recherchée dans la liste",
          "correct": false,
          "feedback": "Erreur : la cible ne change pas pendant la\nrecherche, ce n'est pas une quantité\ndécroissante.\n"
        },
        {
          "text": "L'indice gauche `g` de l'intervalle de recherche",
          "correct": false,
          "feedback": "Erreur : `g` peut rester constant si on met\nà jour seulement `d` ; et il **croît** quand\nil change.\n"
        },
        {
          "text": "La taille de l'intervalle de recherche, `d - g + 1`",
          "correct": true,
          "feedback": "Bonne réponse : c'est précisément cette\nquantité qui décroît strictement à chaque\nitération (et même elle est divisée par 2\nen moyenne). Quand elle atteint 0, on sort\nde la boucle.\n"
        }
      ],
      "explanation": "Notion de variant : indispensable pour prouver\nla terminaison d'une boucle. La taille de\nl'intervalle est l'exemple canonique pour la\ndichotomie."
    },
    {
      "id": "q24",
      "difficulty": 3,
      "skills": [
        "optimisation"
      ],
      "title": "Recherche en mémoire externe",
      "statement": "On dispose d'un fichier trié de **10 milliards**\nd'enregistrements (sur disque). Quel impact\npratique a la dichotomie ?",
      "options": [
        {
          "text": "Cela ne fonctionne pas hors mémoire vive",
          "correct": false,
          "feedback": "Erreur : la dichotomie ne nécessite pas de\ntout charger en mémoire. C'est même son\nintérêt.\n"
        },
        {
          "text": "10 milliards d'accès disque restent nécessaires pour conclure",
          "correct": false,
          "feedback": "Erreur : ce serait la linéaire, donc plusieurs\njours de calcul.\n"
        },
        {
          "text": "Aucun, la lecture séquentielle est aussi rapide",
          "correct": false,
          "feedback": "Erreur : avec 10 milliards d'enregistrements,\nla lecture séquentielle est très lente.\n"
        },
        {
          "text": "~ 34 accès disque suffisent (log₂(10¹⁰) ≈ 33,2)",
          "correct": true,
          "feedback": "Bonne réponse : la dichotomie permet de\nconsulter une base gigantesque en très peu\nd'accès. C'est ce qui permet aux moteurs de\nrecherche, BDD et autres systèmes\nd'indexer efficacement de gros volumes.\n"
        }
      ],
      "explanation": "Idée centrale : à chaque étape, on **divise**\nl'intervalle de recherche par deux. C'est ce\nqui donne le coût logarithmique."
    },
    {
      "id": "q25",
      "difficulty": 3,
      "skills": [
        "precondition-violee"
      ],
      "title": "Précondition violée",
      "statement": "Un élève applique à la liste\n`[42, 31, 25, 17, 11, 7, 3]`, triée par ordre\n**décroissant**, la dichotomie écrite pour une\nliste croissante :\n```python\ndef dicho(liste, cible):\n    g, d = 0, len(liste) - 1\n    while g <= d:\n        m = (g + d) // 2\n        if liste[m] == cible:\n            return m\n        elif liste[m] < cible:\n            g = m + 1\n        else:\n            d = m - 1\n    return -1\n```\nQue renvoie `dicho(liste, 31)` ?",
      "options": [
        {
          "text": "`-1`, alors que 31 est bien dans la liste",
          "correct": true,
          "feedback": "Bonne réponse : au premier tour, `m = 3` et\n`liste[3] = 17 < 31`, donc l'algorithme\npoursuit à droite (`g = 4`) alors que 31 est\nà gauche. Il teste ensuite 7 puis 3, toujours\nplus petits que 31, et sort avec `g > d` :\n-1, sans la moindre erreur d'exécution.\n"
        },
        {
          "text": "`1`, l'indice de 31 dans la liste",
          "correct": false,
          "feedback": "Erreur : l'algorithme ne teste jamais\nl'indice 1. Dès le premier tour, la\ncomparaison `17 < 31` l'envoie dans la\nmoitié droite, où 31 ne se trouve pas, et il\nn'en revient jamais.\n"
        },
        {
          "text": "`3`, l'indice du milieu testé en premier",
          "correct": false,
          "feedback": "Erreur : `liste[3]` vaut 17 et non 31, donc\nl'égalité échoue et la recherche continue.\nL'indice du milieu n'est renvoyé que si\nl'élément qui s'y trouve est la cible.\n"
        },
        {
          "text": "Une erreur `IndexError`, car les indices sortent de la liste",
          "correct": false,
          "feedback": "Erreur : `g` et `d` restent entre 0 et 6 ;\nla boucle `while g <= d` s'arrête proprement\ndès que `g` dépasse `d`. Le résultat est\nfaux, mais aucune exception n'est levée.\n"
        }
      ],
      "explanation": "Une précondition violée ne provoque pas\nforcément de plantage : ici la dichotomie\nrenvoie une réponse fausse sans avertir. D'où\nla nécessité de trier la liste (ou de vérifier\nqu'elle l'est) avant d'appliquer l'algorithme."
    },
    {
      "id": "q26",
      "difficulty": 2,
      "skills": [
        "recursive"
      ],
      "title": "Version récursive",
      "statement": "Quel code implémente correctement une **version récursive** de la recherche dichotomique\n(renvoie l'indice ou `-1`) ?",
      "options": [
        {
          "text": "```python\ndef dicho(liste, cible, g, d):\n    if g >= d:\n        return -1\n    m = (g + d) // 2\n    if liste[m] == cible:\n        return m\n    return dicho(liste, cible, g, d)\n```\n",
          "correct": false,
          "feedback": "Erreur double : le cas de base `g >= d` rate\nla cible quand l'intervalle se réduit à un\nseul élément (cas similaire au bug `while g < d:`),\net l'appel récursif sans mise à jour de\n`g, d` provoque une récursion infinie si la\ncible n'est pas en milieu.\n"
        },
        {
          "text": "```python\ndef dicho(liste, cible, g, d):\n    if g > d:\n        return -1\n    m = (g + d) // 2\n    if liste[m] == cible:\n        return m\n    elif liste[m] < cible:\n        return dicho(liste, cible, m + 1, d)\n    else:\n        return dicho(liste, cible, g, m - 1)\n```\n",
          "correct": true,
          "feedback": "Bonne réponse : cas de base `g > d` (intervalle\nvide), sinon trois cas selon la comparaison\nau milieu. La récursion porte sur des bornes\nmises à jour, exactement comme dans la version\nitérative.\n"
        },
        {
          "text": "```python\ndef dicho(liste, cible, g, d):\n    m = (g + d) // 2\n    if liste[m] == cible:\n        return m\n    elif liste[m] < cible:\n        return dicho(liste, cible, m + 1, d)\n    else:\n        return dicho(liste, cible, g, m - 1)\n```\n",
          "correct": false,
          "feedback": "Erreur : il manque le cas de base. Quand la\ncible est absente, rien n'arrête les appels :\nla fonction se rappelle indéfiniment sur un\nintervalle vide (les indices négatifs restant\nlicites en Python, aucune exception d'accès\nn'est levée) et Python finit par lever une\n`RecursionError` (récursion infinie).\n"
        },
        {
          "text": "```python\ndef dicho(liste, cible):\n    if len(liste) == 0:\n        return -1\n    m = len(liste) // 2\n    if liste[m] == cible:\n        return m\n    elif liste[m] < cible:\n        return dicho(liste[m+1:], cible)\n    else:\n        return dicho(liste[:m], cible)\n```\n",
          "correct": false,
          "feedback": "Erreur : le découpage par tranche\n`liste[m+1:]` recopie une partie de la liste\n(coût O(n) à chaque appel), ce qui ruine\nl'avantage de la dichotomie. De plus, l'indice\nrenvoyé est relatif à la sous-liste, donc\nincorrect par rapport à la liste originale.\n"
        }
      ],
      "explanation": "Avantage de la version récursive : code plus\ncourt, qui suit naturellement le raisonnement\n« diviser pour régner ». La profondeur des\nappels reste en log₂(n) : même un milliard\nd'éléments ne demande qu'une trentaine d'appels\nimbriqués, très loin de la limite de Python\n(environ 1000). Le dépassement de pile n'est\ndonc pas un risque pour la dichotomie."
    },
    {
      "id": "q27",
      "difficulty": 3,
      "skills": [
        "doublement-taille"
      ],
      "title": "Doublement de la taille",
      "statement": "Une recherche dichotomique sur une liste de\ntaille `n` effectue au pire `k` comparaisons.\nCombien de comparaisons effectue-t-elle au pire\nsur une liste de taille `2n` ?",
      "options": [
        {
          "text": "`2k`",
          "correct": false,
          "feedback": "Erreur : la dichotomie n'est pas linéaire en\nn. Doubler la taille n'ajoute pas k\ncomparaisons mais une seule.\n"
        },
        {
          "text": "`k`",
          "correct": false,
          "feedback": "Erreur : ce serait dire que la complexité ne\ndépend pas de la taille, ce qui est faux. La\ndichotomie ajoute bien une comparaison\nquand on double n.\n"
        },
        {
          "text": "`k + 1`",
          "correct": true,
          "feedback": "Bonne réponse : la complexité est en\n`log₂(n)`. Or `log₂(2n) = log₂(n) + 1`. Donc\ndoubler la taille n'ajoute qu'**une seule**\ncomparaison au pire. C'est la signature\nfondamentale d'un algorithme logarithmique.\n"
        },
        {
          "text": "`k²`",
          "correct": false,
          "feedback": "Erreur : il n'y a pas de carré dans\n`log(2n)`. Cela donnerait une complexité\nbeaucoup trop élevée.\n"
        }
      ],
      "explanation": "Lecture pratique : on peut multiplier par mille\nla taille d'une liste sans payer plus de dix\ncomparaisons supplémentaires (`log₂(1000) ≈ 10`).\nC'est ce qui rend la dichotomie incontournable\npour les très gros volumes."
    },
    {
      "id": "q28",
      "difficulty": 3,
      "skills": [
        "dichotomie-frontiere"
      ],
      "title": "Dichotomie de frontière",
      "statement": "On dispose d'une liste triée de booléens (par\nexemple `[False, False, False, True, True, True]`)\net on cherche l'indice de la **première**\noccurrence de `True`. Quel principe permet de\nrésoudre ce problème en `O(log n)` ?",
      "options": [
        {
          "text": "Compter les `True` puis renvoyer leur position",
          "correct": false,
          "feedback": "Erreur : compter les `True` (ou les `False`)\ndemande de parcourir toute la liste, donc\n`O(n)`. On perd l'avantage logarithmique.\n"
        },
        {
          "text": "Une recherche linéaire suffit, on ne peut pas faire mieux",
          "correct": false,
          "feedback": "Erreur : on peut faire mieux. La liste est\ntriée (tous les `False` avant tous les\n`True`), ce qui permet la dichotomie.\n"
        },
        {
          "text": "Trier une nouvelle fois l'intégralité de la liste de booléens",
          "correct": false,
          "feedback": "Erreur : la liste est déjà triée par\nhypothèse. Trier une liste déjà triée ne\nchange rien, et coûte `O(n log n)`.\n"
        },
        {
          "text": "Une dichotomie adaptée, resserrée vers la frontière",
          "correct": true,
          "feedback": "Bonne réponse : selon que le milieu est\n`True` ou `False`, on conserve la moitié\nqui contient la frontière. La dichotomie\nne se limite pas à la recherche\nd'égalité : dès qu'une propriété est\n**monotone** (vraie partout au-dessus\nd'un seuil, fausse en dessous), on peut\nchercher le seuil en `O(log n)`.\n"
        }
      ],
      "explanation": "Schéma général : si `P(i)` est une propriété\nmonotone sur `i` (par exemple : « la valeur en\nposition `i` est `True` »), on trouve la\nfrontière en `O(log n)` par dichotomie. Très\nutile pour des problèmes comme « plus petite\ncapacité satisfaisant un critère », « plus\ngrande valeur respectant une contrainte », etc."
    }
  ]
}