<?xml version="1.0" encoding="UTF-8"?>
<quiz>
<question type="category">
  <category>
    <text>$course$/QCM de NSI/Terminale/Récursivité</text>
  </category>
  <info format="html">
    <text><![CDATA[<p>Définition d'une fonction récursive (cas de base, appel récursif),<br/>
déroulement de la pile d'appels, terminaison, exemples classiques<br/>
(factorielle, Fibonacci, somme, parcours d'arbre, tours de Hanoï),<br/>
récursivité simple vs double, complexité et mémoïsation.</p>]]></text>
  </info>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q01 : Définition d'une fonction récursive</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle propriété caractérise une fonction récursive ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Une fonction récursive comporte deux ingrédients indispensables :<br/>
au moins un <strong>cas de base</strong> (qui renvoie une valeur sans appel<br/>
récursif) et un ou plusieurs <strong>appels récursifs</strong> sur un<br/>
sous-problème strictement plus petit, pour garantir la<br/>
terminaison.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Elle s'appelle elle-même, avec au moins un cas de base qui ne fait pas d'appel récursif</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : une fonction récursive est définie par un ou<br/>
plusieurs cas de base (qui arrêtent la récursion) et un ou<br/>
plusieurs appels à elle-même sur un sous-problème plus petit.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle prend obligatoirement deux paramètres : une valeur et une accumulation</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : une fonction récursive peut n'avoir qu'un seul<br/>
paramètre (par exemple factorielle(n)). L'accumulateur est<br/>
un schéma possible, pas une obligation.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle renvoie obligatoirement une liste qui se construit pas à pas</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la valeur de retour peut être de n'importe quel type<br/>
(entier, booléen, chaîne, liste, etc.).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle utilise une boucle while qui décompte une variable</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est la définition d'une fonction itérative. La<br/>
récursivité remplace justement les boucles par des appels<br/>
successifs de la fonction à elle-même.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q02 : Nécessité du cas de base</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Que se passe-t-il en Python si une fonction récursive ne possède<br/>
aucun cas de base ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le cas de base est ce qui garantit la terminaison de la<br/>
récursion. Sans lui, on a une boucle infinie qui empile des<br/>
appels jusqu'à dépassement de la pile.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle renvoie systématiquement None</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : None est la valeur renvoyée par défaut quand une<br/>
fonction n'a pas d'instruction return. Ici, le problème<br/>
est la non-terminaison, pas la valeur de retour.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle renvoie 0</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : aucune valeur de retour spéciale n'est associée à<br/>
l'absence de cas de base.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Elle s'exécute infiniment et finit par lever une erreur RecursionError</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : Python limite la profondeur de la pile<br/>
d'appels (par défaut autour de 1000). Sans cas de base,<br/>
la fonction s'auto-appelle indéfiniment et provoque cette<br/>
erreur.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le compilateur la rejette avant exécution</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : Python n'analyse pas la sémantique de la fonction<br/>
avant de l'exécuter. Sans cas de base, l'erreur survient à<br/>
l'exécution, pas à la compilation.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q03 : Rôle de la pile d'appels</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quand une fonction f s'appelle récursivement, que stocke la<br/>
pile d'appels pendant l'exécution ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>C'est la limite physique de la pile (typiquement quelques<br/>
milliers d'appels) qui rend les récursions très profondes<br/>
problématiques en Python : un parcours linéaire d'une liste de<br/>
10\ 000 éléments par récursion plante avec<br/>
RecursionError.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une copie complète de la mémoire à chaque appel</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce serait inefficace et inutile. Seules les<br/>
informations propres à l'appel courant sont empilées.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Le contexte de chaque appel : paramètres, variables locales et point de retour</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : à chaque appel, Python empile un cadre<br/>
d'exécution contenant les paramètres, les variables locales<br/>
et l'instruction où reprendre une fois l'appel terminé. Au<br/>
retour, ce cadre est dépilé.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Uniquement la valeur de retour de chaque appel</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la valeur de retour n'est connue qu'à la <strong>fin</strong> de<br/>
chaque appel. La pile sert justement à conserver l'état<br/>
pendant que les appels imbriqués se déroulent.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le code source de la fonction, dupliqué à chaque appel</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : le code source de la fonction n'est stocké qu'une<br/>
seule fois. La pile contient seulement le <strong>contexte<br/>
d'exécution</strong> de chaque appel.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q04 : Évaluation d'une récurrence simple</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On considère la fonction Python suivante :</p>
<p>`<code><br/>
def f(n):<br/>
    if n == 0:<br/>
        return 1<br/>
    return 2 * f(n - 1)<br/>
</code></p>
<p>Que renvoie l'appel f(4)` ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette fonction calcule 2ⁿ. La récurrence f(n) = 2 · f(n-1)<br/>
avec f(0) = 1 donne immédiatement f(n) = 2ⁿ.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>32</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est f(5) = 2⁵. Une multiplication en trop.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>16</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : f(4) = 2 \cdot f(3) = 2 \cdot 2 \cdot f(2) = \ldots = 2^4 \cdot f(0) = 2^4 = 16.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>4</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : confusion entre l'argument n et la valeur de<br/>
retour. Ici f(4) = 2⁴, pas 4.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>8</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est f(3) = 2³. Le calcul de f(4) ajoute une<br/>
multiplication par 2.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q05 : Identifier le cas de base de la factorielle</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On définit factorielle ainsi :</p>
<p>`<code><br/>
def factorielle(n):<br/>
    if n &lt;= 1:<br/>
        return 1<br/>
    return n * factorielle(n - 1)<br/>
</code></p>
<p>Quel est le rôle de la condition if n &lt;= 1: return 1` ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Sans ce cas de base, la fonction continuerait à décrémenter n<br/>
indéfiniment, en passant par 0, -1, -2, \ldots, et<br/>
provoquerait une RecursionError. Le cas de base est l'élément<br/>
central de toute récursion bien définie.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle assure que n reste positif pendant l'exécution</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la condition n'a pas d'effet sur les autres appels.<br/>
Son rôle est d'arrêter la récursion quand n atteint 1.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle initialise une variable de comptage</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : aucune variable n'est initialisée. C'est une<br/>
condition de fin qui interrompt la récursion.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>C'est le cas de base, qui arrête la récursion sans nouvel appel</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : c'est le cas de base. Pour n ≤ 1, la<br/>
fonction renvoie 1 directement, sans appel récursif. C'est<br/>
ce qui garantit la terminaison.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle gère uniquement les cas d'erreur quand n est négatif</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la condition couvre n ≤ 1 (donc aussi n = 0<br/>
et n = 1), pas seulement les cas négatifs. Elle joue un<br/>
rôle algorithmique, pas seulement défensif.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q06 : Identifier le cas de base de l'algorithme d'Euclide</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Dans la fonction suivante, qui calcule le PGCD de deux entiers,<br/>
quel est le cas de base ?</p>
<p>`<code><br/>
def pgcd(a, b):<br/>
    if b == 0:<br/>
        return a<br/>
    return pgcd(b, a % b)<br/>
</code>`</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>L'algorithme d'Euclide repose sur la propriété<br/>
\text{PGCD}(a, b) = \text{PGCD}(b, a \mod b), avec le cas de<br/>
base \text{PGCD}(a, 0) = a. À chaque appel, b diminue<br/>
strictement (puisque a \mod b &lt; b), garantissant la<br/>
terminaison.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>a == 0</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la condition d'arrêt teste b, pas a.<br/>
L'algorithme d'Euclide réduit progressivement b vers 0.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>b == 0</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : quand b vaut 0, la fonction renvoie a<br/>
directement, sans nouvel appel. C'est le cas de base.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>a == b</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce cas n'est pas géré explicitement. Dans cet<br/>
algorithme, l'égalité a == b mène à pgcd(b, 0) au prochain<br/>
appel, qui déclenche le vrai cas de base.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>a % b == 0</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce n'est pas la condition utilisée. Si a % b == 0,<br/>
l'appel récursif suivant sera pgcd(b, 0), qui déclenchera<br/>
le vrai cas de base à l'appel suivant.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q07 : Récursivité simple ou récursivité double</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Une fonction qui contient <strong>deux appels récursifs</strong> sur des<br/>
sous-problèmes différents est dite :</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La distinction simple/double est importante pour la complexité :<br/>
une récursivité simple a typiquement une complexité linéaire,<br/>
tandis qu'une récursivité double naïve a une complexité<br/>
exponentielle (cas de Fibonacci sans mémoïsation).</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Une récursivité double (ou multiple)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : c'est typique du parcours d'arbres binaires<br/>
(un appel pour le sous-arbre gauche, un pour le droit), de<br/>
Fibonacci (f(n-1) + f(n-2)) ou de la stratégie « diviser<br/>
pour régner ».</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une récursivité terminale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la récursivité terminale désigne un cas où l'appel<br/>
récursif est la <strong>dernière</strong> opération de la fonction<br/>
(optimisable par certains compilateurs).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une récursivité simple</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : on parle de récursivité simple quand la fonction ne<br/>
contient <strong>qu'un seul</strong> appel récursif (par exemple<br/>
factorielle).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une récursivité croisée</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la récursivité croisée concerne deux fonctions qui<br/>
s'appellent mutuellement (par exemple pair(n) et<br/>
impair(n)), pas deux appels d'une même fonction.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q08 : Garantir la terminaison</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle propriété l'argument d'un appel récursif doit-il vérifier<br/>
pour garantir que la fonction termine ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>En toute rigueur, on associe à chaque appel un entier appelé<br/>
« variant », strictement décroissant et minoré par 0. Pour<br/>
factorielle(n), le variant est n. Pour une fonction sur<br/>
L[1:], c'est len(L).</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Il doit être un entier</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce n'est pas une condition. Les arguments peuvent<br/>
être des chaînes, des listes, des arbres, etc. Ce qui compte,<br/>
c'est qu'ils diminuent en « taille » à chaque appel.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Il doit augmenter strictement à chaque appel</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : si l'argument augmente, on s'éloigne du cas de<br/>
base au lieu de s'en rapprocher. La fonction ne termine<br/>
jamais.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Il doit rester constant tant que le cas de base n'est pas atteint</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : si l'argument reste constant, la fonction se rappelle<br/>
avec les mêmes valeurs et ne termine jamais.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Il doit décroître strictement à chaque appel, vers le cas de base</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : pour qu'une fonction récursive termine, il<br/>
faut que la suite des arguments des appels successifs<br/>
atteigne le cas de base en un nombre fini d'étapes. La<br/>
décroissance stricte d'une quantité positive (la « variant »)<br/>
assure cette terminaison.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q09 : Lecture d'une récursion sur les chaînes</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Dans la fonction suivante, quel est le cas de base ?</p>
<p>`<code><br/>
def compte(chaine):<br/>
    if chaine == "":<br/>
        return 0<br/>
    return 1 + compte(chaine[1:])<br/>
</code>`</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette fonction calcule en réalité la longueur de la chaîne. Le<br/>
cas de base est la chaîne vide (longueur 0) ; chaque appel<br/>
récursif retire le premier caractère et ajoute 1.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>chaine == ""</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : quand la chaîne est vide, la fonction renvoie<br/>
0 sans nouvel appel. C'est le cas de base, qui arrête la<br/>
récursion.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>1 + compte(chaine[1:])</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est l'instruction de l'appel récursif. Le cas de<br/>
base est la branche qui ne fait <strong>pas</strong> d'appel.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La fonction n'a pas de cas de base</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la branche if chaine == "": return 0 arrête bien<br/>
la récursion sans nouvel appel.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>chaine[1:]</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : chaine[1:] est l'argument de l'appel récursif, pas<br/>
le cas de base. Le cas de base est ce qui <strong>arrête</strong> la<br/>
récursion.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q10 : Récursivité et définition par récurrence</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle suite mathématique peut être implémentée le plus<br/>
naturellement par une fonction récursive simple (un seul appel<br/>
récursif par cas) ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La récursion simple correspond aux suites du premier ordre<br/>
(uₙ dépend uniquement de uₙ₋₁). Une récurrence d'ordre<br/>
2 (Fibonacci) demande une récursion double, plus coûteuse en<br/>
temps si on n'utilise pas de mémoïsation.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une suite quelconque, tirée au hasard</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : il faut une définition claire (cas initial et<br/>
relation de récurrence) pour pouvoir écrire une fonction<br/>
récursive.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une suite définie par uₙ = f(uₙ₋₁, uₙ₋₂)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : cette définition demande <strong>deux</strong> valeurs<br/>
précédentes, donc deux appels récursifs (récursivité double).<br/>
La suite de Fibonacci en est l'exemple classique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Une suite définie par u₀ et uₙ = f(uₙ₋₁)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on a un cas de base (u₀) et un appel<br/>
récursif (calcul de uₙ à partir de uₙ₋₁). La<br/>
structure correspond exactement à une récursion simple.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une suite définie uniquement par sa valeur initiale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : sans relation de récurrence, il n'y a pas d'appel<br/>
récursif à faire. Une seule valeur ne nécessite pas de<br/>
fonction récursive.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q11 : Déroulement d'une somme récursive</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On considère :</p>
<p>`<code><br/>
def somme(L):<br/>
    if L == []:<br/>
        return 0<br/>
    return L[0] + somme(L[1:])<br/>
</code></p>
<p>Que renvoie somme([3, 5, 2])` ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette fonction est l'archétype du parcours récursif d'une liste :<br/>
cas de base sur la liste vide (0), et appel récursif sur le<br/>
reste de la liste après extraction du premier élément.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>5</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est l'élément du milieu de la liste, mais la<br/>
fonction additionne <strong>tous</strong> les éléments.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>30</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : confusion possible avec un produit ou un calcul<br/>
erroné. La fonction additionne les éléments, elle ne les<br/>
multiplie pas.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>0</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est la valeur renvoyée pour la <strong>liste vide</strong>, qui<br/>
intervient au plus profond de la pile, mais qui est ensuite<br/>
additionnée aux éléments dépilés.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>10</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : 3 + 5 + 2 = 10. En déroulant :<br/>
3 + \text{somme}([5,2]) = 3 + (5 + \text{somme}([2])) = 3 + (5 + (2 + 0)) = 10.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q12 : Compter les chiffres d'un entier</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On considère :</p>
<p>`<code><br/>
def nb_chiffres(n):<br/>
    if n &lt; 10:<br/>
        return 1<br/>
    return 1 + nb_chiffres(n // 10)<br/>
</code></p>
<p>Que renvoie nb_chiffres(4073)` ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La division entière par 10 retire le dernier chiffre. Le cas<br/>
de base n &lt; 10 correspond à un nombre à un seul chiffre<br/>
(longueur 1). Cette technique est très utile pour parcourir un<br/>
entier chiffre par chiffre sans le convertir en chaîne.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>3</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : oubli d'un chiffre. L'entier 4073 comporte 4<br/>
chiffres : 4, 0, 7, 3.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>4</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on déroule 1 + \text{nb\_chiffres}(407) = 1 + 1 + \text{nb\_chiffres}(40) = 1 + 1 + 1 + \text{nb\_chiffres}(4) = 1 + 1 + 1 + 1 = 4.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>10</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : confusion possible avec la base 10 utilisée pour<br/>
extraire les chiffres. La fonction compte le nombre de<br/>
divisions par 10 nécessaires pour atteindre un nombre à un<br/>
chiffre.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>5</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : un chiffre en trop. Quand n &lt; 10, la fonction<br/>
renvoie 1 et n'effectue pas d'appel supplémentaire.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q13 : Compléter une fonction récursive</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour que la fonction suivante renvoie le nombre d'occurrences<br/>
d'un caractère c dans une chaîne s, par quoi faut-il<br/>
remplacer les pointillés ?</p>
<p>`<code><br/>
def occurrences(s, c):<br/>
    if s == "":<br/>
        return ...<br/>
    if s[0] == c:<br/>
        return 1 + occurrences(s[1:], c)<br/>
    return occurrences(s[1:], c)<br/>
</code>`</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le cas de base d'un comptage récursif est presque toujours 0<br/>
(aucun élément trouvé dans la structure vide). La récursion<br/>
ajoute ensuite 1 chaque fois qu'on rencontre un élément<br/>
cherché.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>0</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : une chaîne vide contient zéro occurrence de<br/>
n'importe quel caractère. C'est la valeur neutre pour la<br/>
somme finale.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>None</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : None ne pourrait pas s'additionner avec 1 dans<br/>
la branche récursive, et la fonction est censée renvoyer un<br/>
comptage.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>1</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : une chaîne vide ne contient aucune occurrence d'un<br/>
caractère, le cas de base doit donc renvoyer 0.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>c</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la fonction est censée renvoyer un entier (un<br/>
comptage), pas un caractère.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q14 : Maximum d'une liste sans `max`</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On souhaite écrire récursivement le maximum d'une liste non vide.<br/>
Quel est le <strong>cas de base</strong> approprié ?</p>
<p>`<code><br/>
def maximum(L):<br/>
    if ...:<br/>
        return L[0]<br/>
    ...<br/>
</code>`</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Une fois le cas de base posé (len(L) == 1), l'appel récursif<br/>
compare le premier élément au maximum du reste de la liste :<br/>
m = maximum(L[1:]) puis renvoie L[0] if L[0] &gt; m else m.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L[0] &gt; L[-1]</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est une comparaison entre deux éléments, pas un<br/>
critère d'arrêt sur la taille de la liste.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>L == []</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : sur une liste vide, L[0] provoquerait une erreur<br/>
(IndexError). Le cas de base doit garantir que L[0]<br/>
existe.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>len(L) == 2</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : on n'a alors pas couvert le cas \text{len}(L) = 1,<br/>
qui peut survenir comme appel récursif terminal. La fonction<br/>
ne terminerait pas correctement.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>len(L) == 1</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : le maximum d'une liste à un seul élément est<br/>
cet élément lui-même. C'est le cas de base le plus naturel<br/>
pour cette fonction.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q15 : Inversion récursive d'une liste</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle expression complète correctement la fonction miroir<br/>
ci-dessous, qui doit renvoyer la liste inversée ?</p>
<p>`<code><br/>
def miroir(L):<br/>
    if L == []:<br/>
        return []<br/>
    return ...<br/>
</code>`</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>L'idée centrale : pour inverser une liste, on inverse la queue<br/>
et on y accole la tête. Cette construction est élégante mais<br/>
coûteuse en mémoire (à chaque appel, on crée une nouvelle liste<br/>
avec +).</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>miroir(L[1:]) + [L[0]]</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on inverse récursivement le reste de la<br/>
liste, puis on place L[0] à la fin. Exemple :<br/>
[1, 2, 3] \to \text{miroir}([2, 3]) + [1] = [3, 2] + [1] = [3, 2, 1].</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>miroir(L[0]) + miroir(L[1:])</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : L[0] est un élément simple, pas une liste : on ne<br/>
peut pas l'inverser récursivement.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>miroir(L)[1:]</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : cette définition est circulaire (la fonction<br/>
s'appelle sur la même valeur), donc ne termine jamais.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>[L[0]] + miroir(L[1:])</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : cette construction renverrait la liste <strong>inchangée</strong>.<br/>
Pour l'inverser, l'élément L[0] doit aller à la fin, pas au<br/>
début.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q16 : Croissance d'une population</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Un biologiste modélise une population de bactéries qui double<br/>
chaque heure, en partant d'une seule bactérie. Si la fonction<br/>
récursive est :</p>
<p>`<code><br/>
def population(n):<br/>
    if n == 0:<br/>
        return 1<br/>
    return 2 * population(n - 1)<br/>
</code>`</p>
<p>Combien de bactéries y a-t-il après 10 heures ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cet exemple illustre la croissance exponentielle : la population<br/>
double à chaque pas. Après 20 heures, on aurait<br/>
2²⁰ ≈ 10⁶ bactéries ; après 30 heures, plus d'un<br/>
milliard.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>100</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la croissance n'est pas géométrique de raison 10.<br/>
Chaque heure, la population est multipliée par 2, pas par<br/>
10.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>1024</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : \text{population}(10) = 2^{10} = 1024. Une<br/>
fois la fonction reconnue comme calculant 2ⁿ, on applique<br/>
la formule directement.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>20</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : confusion avec une croissance linéaire (2 · n).<br/>
Or la population <strong>double</strong>, donc on a une croissance<br/>
exponentielle.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>2048</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est 2¹¹, soit population après 11 heures,<br/>
pas 10.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q17 : Récursivité sur un arbre binaire</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On parcourt un arbre binaire avec la fonction :</p>
<p>`<code><br/>
def cherche(elt, arbre):<br/>
    if arbre is None:<br/>
        return False<br/>
    if arbre.valeur == elt:<br/>
        return True<br/>
    return cherche(elt, arbre.gauche) or cherche(elt, arbre.droite)<br/>
</code>`</p>
<p>Combien d'appels récursifs cette fonction génère-t-elle au<br/>
maximum dans une seule exécution ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le or court-circuite l'appel droit dès que l'appel gauche<br/>
renvoie True, donc dans la pratique on n'explore pas toujours<br/>
les deux sous-arbres. Mais dans le pire cas (élément absent ou<br/>
présent uniquement à droite), les deux appels sont effectués.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Aucun, c'est une fonction itérative déguisée</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la fonction contient bien deux appels récursifs<br/>
(cherche(elt, arbre.gauche) et<br/>
cherche(elt, arbre.droite)). Elle n'est pas itérative.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Deux, un sur chaque sous-arbre</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : c'est de la récursivité double. Chaque appel<br/>
peut générer deux nouveaux appels (gauche et droit), ce qui<br/>
explore exhaustivement l'arbre dans le pire cas.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Autant que d'éléments dans la liste représentant l'arbre</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la fonction n'est pas écrite sur une liste mais sur<br/>
une structure d'arbre. Chaque appel travaille sur un sous-arbre.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Un seul, sur le sous-arbre gauche</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : si l'élément n'est pas dans le sous-arbre gauche,<br/>
on explore aussi le sous-arbre droit. Cela fait potentiellement<br/>
deux appels par nœud.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q18 : Limite pratique de la récursion en Python</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour quelle raison principale Python lève-t-il une<br/>
RecursionError lors d'une récursion trop profonde, même si la<br/>
fonction comporte un cas de base correct ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette limite est une protection : sans elle, une fonction<br/>
récursive mal conçue saturerait la mémoire de la pile et ferait<br/>
planter le système. Elle implique que certains algorithmes<br/>
fonctionnant sur de grandes structures (parcours d'une liste de<br/>
plusieurs milliers d'éléments) doivent être écrits de manière<br/>
itérative en Python.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La pile d'appels du programme a une taille limite (par défaut autour de 1000)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : Python fixe par défaut une profondeur<br/>
maximale de récursion d'environ 1000 pour éviter qu'une<br/>
erreur de programmation ne consomme toute la mémoire de la<br/>
pile. On peut augmenter cette limite avec<br/>
sys.setrecursionlimit(), mais ce n'est pas une bonne<br/>
pratique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le cas de base met trop de temps à s'évaluer</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : le temps d'évaluation d'une condition est<br/>
négligeable. Le problème est la <strong>mémoire</strong> consommée par la<br/>
pile d'appels.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La fonction utilise trop de variables locales</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce n'est pas le nombre de variables locales qui<br/>
compte, mais le nombre d'appels imbriqués.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Python ne sait pas gérer la récursivité</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : Python gère parfaitement la récursivité, mais avec<br/>
une limite pratique sur la profondeur de la pile.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q19 : Complexité de Fibonacci récursif naïf</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle est la complexité en temps de la fonction suivante,<br/>
appelée pour calculer fibonacci(n) ?</p>
<p>`<code><br/>
def fibonacci(n):<br/>
    if n &lt;= 1:<br/>
        return n<br/>
    return fibonacci(n - 1) + fibonacci(n - 2)<br/>
</code>`</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>fibonacci(35) génère plusieurs milliards d'appels avec cette<br/>
version naïve. C'est un cas typique où la mémoïsation (stocker<br/>
les résultats déjà calculés dans un dictionnaire) ramène la<br/>
complexité à O(n).</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>O(n²)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la complexité n'est pas polynomiale mais<br/>
<strong>exponentielle</strong>. L'arbre d'appels double de taille à chaque<br/>
niveau.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>O(n)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est la complexité de la version itérative ou<br/>
mémoïsée. La version naïve recalcule plusieurs fois les<br/>
mêmes valeurs.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>O(\log n)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : aucune division n'est effectuée sur n ; on le<br/>
décrémente seulement de 1 ou 2. La complexité ne peut<br/>
donc pas être logarithmique.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>O(2ⁿ)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : chaque appel génère deux appels imbriqués,<br/>
créant un arbre binaire de profondeur n. Le nombre d'appels<br/>
croît comme 2ⁿ (plus précisément φⁿ avec<br/>
φ le nombre d'or).</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q20 : Principe de la mémoïsation</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Qu'est-ce que la <strong>mémoïsation</strong> d'une fonction récursive ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Pour Fibonacci, la mémoïsation transforme une complexité<br/>
O(2ⁿ) en O(n). Python propose un décorateur<br/>
@functools.lru_cache qui implémente automatiquement la<br/>
mémoïsation sur n'importe quelle fonction.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Le stockage en cache des résultats déjà calculés, pour les réutiliser sans les recalculer</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on conserve dans un dictionnaire (ou un<br/>
tableau) les résultats déjà obtenus. Avant chaque calcul, on<br/>
vérifie si le résultat est déjà connu ; si oui, on le<br/>
renvoie directement.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Une technique pour réduire la profondeur de la pile d'appels</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la mémoïsation n'agit pas sur la profondeur de la<br/>
pile mais sur le <strong>nombre d'appels effectifs</strong> : elle évite<br/>
de recalculer ce qui a déjà été calculé.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La conversion automatique d'une fonction récursive en boucle</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce serait la dérécursivation. La mémoïsation ne<br/>
modifie pas la structure de la fonction, elle ajoute un<br/>
cache.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La compression des appels récursifs en un seul</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : aucune fusion d'appels n'a lieu. La mémoïsation<br/>
conserve la même structure d'appels, mais évite les<br/>
redondances.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q21 : Tours de Hanoï avec deux disques</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour résoudre le problème des tours de Hanoï avec 2 disques<br/>
(déplacer la pile de A vers C en utilisant B), combien de<br/>
déplacements minimum sont nécessaires ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le nombre de déplacements pour n disques est<br/>
T(n) = 2ⁿ - 1, donné par la récurrence<br/>
T(n) = 2 · T(n-1) + 1 avec T(1) = 1. Pour n = 64<br/>
(légende des moines de Hanoï), il faudrait plus de<br/>
1{,}8 \times 10^{19} déplacements.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>3</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : (1) petit de A vers B, (2) grand de A vers C,<br/>
(3) petit de B vers C. Cela correspond à T(2) = 2² - 1 = 3.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>2</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : il faut d'abord déplacer le petit disque sur l'étape<br/>
intermédiaire B, puis le grand disque vers C, puis ramener le<br/>
petit sur le grand. Cela fait 3 déplacements, pas 2.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>4</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : un déplacement en trop. Avec 2 disques, 3<br/>
déplacements suffisent.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>7</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est T(3) = 2³ - 1, soit le nombre de<br/>
déplacements pour 3 disques, pas 2.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q22 : Récurrence des tours de Hanoï</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Quelle relation de récurrence vérifie T(n), le nombre de<br/>
déplacements minimum pour résoudre les tours de Hanoï avec n<br/>
disques ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette récurrence se résout en T(n) = 2ⁿ - 1. La récursivité<br/>
est ici <strong>double</strong> (deux appels à T(n-1)), ce qui explique la<br/>
croissance exponentielle.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>T(n) = 2 · T(n-1) + 1</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : pour déplacer n disques, on déplace les<br/>
n-1 disques du dessus vers la tour auxiliaire (T(n-1)<br/>
déplacements), puis le grand disque vers la destination (1<br/>
déplacement), puis on ramène les n-1 disques sur la<br/>
destination (T(n-1) déplacements). D'où T(n) = 2T(n-1) + 1.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>T(n) = T(n-1) + T(n-2)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est la récurrence de Fibonacci, sans rapport avec<br/>
les tours de Hanoï. Il n'y a pas de raison de combiner deux<br/>
tailles différentes ici.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>T(n) = T(n-1) + 1</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : cette récurrence donne T(n) = n (croissance<br/>
linéaire), ce qui est faux. La résolution exige un nombre<br/>
exponentiel de déplacements.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>T(n) = n!</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce serait la factorielle, qui croît plus vite que<br/>
2ⁿ. Pour Hanoï, la croissance est exponentielle de base<br/>
2, pas factorielle.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q23 : Choisir entre récursif et itératif</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour calculer la somme des n premiers entiers, on peut écrire<br/>
une boucle ou une fonction récursive. Pour n = 10\ 000 en<br/>
Python, quelle version est préférable ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>En Python, la récursivité est élégante mais coûteuse :<br/>
privilégier l'itératif quand le problème ne se prête pas<br/>
naturellement à la récursion. La récursion brille pour les<br/>
structures naturellement récursives (arbres, listes<br/>
hiérarchiques, fractales) ou pour les algorithmes diviser-pour-<br/>
régner.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La version récursive, plus rapide grâce à la pile d'appels</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : à compter d'une certaine profondeur, la pile<br/>
d'appels devient un handicap (consommation mémoire,<br/>
RecursionError). La récursion n'est pas plus rapide qu'une<br/>
boucle.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Aucune des deux, il faut absolument utiliser une formule fermée</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la formule n(n+1)/2 existe et est en effet<br/>
la solution la plus efficace, mais cela ne disqualifie pas<br/>
la boucle. La récursion, elle, pose un vrai problème de pile.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La version itérative, qui évite le débordement de pile et reste très efficace</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : pour n = 10\ 000, la version récursive<br/>
dépasserait la limite par défaut de la pile Python (∼ 1000 appels). Une boucle for est plus sûre, plus rapide,<br/>
et tout aussi lisible pour ce calcul.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les deux versions sont strictement équivalentes en performance</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la version récursive est plus lente (chaque appel<br/>
coûte plus qu'une itération) et risque de planter par<br/>
débordement de pile.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q24 : Analyse d'une récursion mystère</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On considère :</p>
<p>`<code><br/>
def f(n):<br/>
    if n == 0:<br/>
        return 0<br/>
    if n == 1:<br/>
        return 1<br/>
    return f(n - 1) + f(n - 2) + 1<br/>
</code></p>
<p>Combien d'<strong>appels récursifs au total</strong> sont effectués pour<br/>
calculer f(4)` (en comptant l'appel initial) ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette fonction calcule f(n) = Fₙ₊₁ - 1, où Fₙ est la<br/>
suite de Fibonacci. Plus important pédagogiquement : elle<br/>
montre comment la récursivité double engendre une explosion du<br/>
nombre d'appels : c'est ce que la mémoïsation vient résoudre.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>4</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : on confond avec l'argument. Il faut compter chaque<br/>
appel à f qui apparaît dans l'arbre d'appels, pas la<br/>
valeur de n.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>9</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : f(4) appelle f(3) et f(2). f(3)<br/>
appelle f(2) et f(1). f(2) appelle f(1) et f(0).<br/>
En tout : f(4), f(3), f(2), f(1), f(0), f(2),<br/>
f(1), f(1), f(0) = 9 appels.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>16</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : 16 = 2⁴ surestime largement le nombre d'appels.<br/>
L'arbre n'est pas équilibré jusqu'à la profondeur n.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>5</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : on a oublié les appels redondants. Comme dans<br/>
Fibonacci naïf, certains arguments sont recalculés plusieurs<br/>
fois.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q25 : Effet de la mémoïsation</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On mémoïse la fonction Fibonacci avec un dictionnaire de cache.<br/>
Combien d'appels effectifs (calculs réels, avant cache) sont<br/>
effectués pour fibonacci(20) après mémoïsation ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La mémoïsation transforme une complexité exponentielle en<br/>
complexité linéaire, au prix d'un espace mémoire supplémentaire<br/>
également linéaire (le dictionnaire de cache). C'est l'un des<br/>
exemples les plus marquants du compromis temps/espace en<br/>
algorithmique.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Le même nombre qu'avant mémoïsation</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la mémoïsation change précisément le nombre d'appels<br/>
réels en évitant les recalculs. Ce nombre passe d'O(2ⁿ) à<br/>
O(n).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Exactement 1</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : le cache n'élimine pas la nécessité de calculer<br/>
chaque valeur <strong>au moins une fois</strong>. Pour<br/>
\text{fibonacci}(20), il faut bien évaluer<br/>
\text{fibonacci}(0), \text{fibonacci}(1), \ldots, \text{fibonacci}(20)<br/>
au moins une fois chacun.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Environ 20</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : avec mémoïsation, chaque valeur<br/>
\text{fibonacci}(k) pour k allant de 0 à 20 n'est<br/>
calculée qu'une seule fois. Les appels suivants sur la même<br/>
valeur sont satisfaits par le cache.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Environ 2²⁰ (un million)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est l'ordre de grandeur <strong>sans</strong> mémoïsation. Tout<br/>
l'intérêt du cache est précisément de réduire ce nombre à<br/>
quelque chose de bien plus petit.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q26 : Récursivité terminale</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Une fonction récursive est dite <strong>terminale</strong> quand son<br/>
appel récursif est la <strong>dernière opération</strong> effectuée<br/>
dans la fonction. Parmi les fonctions suivantes,<br/>
laquelle est récursive terminale ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Pourquoi cette propriété est-elle importante ? Une fonction<br/>
récursive terminale peut, en principe, être convertie en<br/>
boucle sans utiliser de pile : on parle d'<strong>élimination<br/>
de la récursivité terminale</strong>. Certains compilateurs<br/>
le font automatiquement (Scheme par exemple), mais pas<br/>
Python, qui conserve toujours la pile d'appels.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>`<code><br/>
def f(n):<br/>
    if n &lt;= 1:<br/>
        return n<br/>
    return f(n - 1) + f(n - 2)<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la fonction effectue une addition après les<br/>
deux appels récursifs. De plus, comme il y a deux<br/>
appels distincts, cette fonction est doublement<br/>
récursive et ne peut pas être terminale.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>`<code><br/>
def f(n):<br/>
    if n == 0:<br/>
        return None<br/>
    print(n)<br/>
    return f(n - 1) + 1<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : après l'appel récursif f(n - 1), on<br/>
ajoute 1. L'opération en attente après le retour<br/>
de l'appel empêche cette fonction d'être récursive<br/>
terminale.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>`<code><br/>
def f(n):<br/>
    if n == 0:<br/>
        return 1<br/>
    return n * f(n - 1)<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : après l'appel récursif f(n - 1), on<br/>
effectue encore une multiplication par n. L'appel<br/>
récursif n'est donc pas la toute dernière opération,<br/>
ce qui empêche cette fonction d'être récursive<br/>
terminale.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>`<code><br/>
def f(n, acc):<br/>
    if n == 0:<br/>
        return acc<br/>
    return f(n - 1, n * acc)<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : l'appel récursif f(n - 1, n * acc)<br/>
est la dernière opération de la fonction (la<br/>
multiplication par n est faite <strong>avant</strong> l'appel,<br/>
dans le calcul du paramètre). Aucune opération n'est<br/>
en attente après l'appel. Cette forme se transforme<br/>
mécaniquement en boucle.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q27 : Convertir une récursion en itération</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On veut transformer la fonction factorielle récursive<br/>
simple en une version itérative équivalente. Quel code<br/>
effectue cette conversion correctement ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Schéma général de la dérécursivation d'une récursion<br/>
simple : un accumulateur initialisé à l'élément neutre,<br/>
une boucle qui parcourt les arguments des appels<br/>
récursifs successifs, et une mise à jour à chaque<br/>
itération. Pour la récursivité double (Fibonacci), on<br/>
conserve souvent les deux dernières valeurs dans deux<br/>
variables.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>`<code><br/>
def factorielle(n):<br/>
    if n == 0:<br/>
        return 1<br/>
    return n * factorielle(n - 1)<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est précisément la <strong>version récursive</strong>.<br/>
La question demandait une version itérative, sans<br/>
appel à la fonction elle-même.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>`<code><br/>
def factorielle(n):<br/>
    return n ** n<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur grossière : nⁿ n'est pas la factorielle de<br/>
n. Pour n = 4, on aurait 256 au lieu de 24.<br/>
La factorielle est définie par n \cdot (n-1) \cdot<br/>
\ldots \cdot 1.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>`<code><br/>
def factorielle(n):<br/>
    produit = 1<br/>
    for i in range(1, n + 1):<br/>
        produit = produit * i<br/>
    return produit<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on remplace les appels récursifs par<br/>
une boucle for qui multiplie successivement par<br/>
1, 2, \ldots, n. La complexité est la même<br/>
(O(n)), mais on évite la pile d'appels et le risque<br/>
de RecursionError sur des grandes valeurs de n.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>`<code><br/>
def factorielle(n):<br/>
    produit = 0<br/>
    for i in range(1, n + 1):<br/>
        produit = produit * i<br/>
    return produit<br/>
</code>`</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : l'élément neutre de la multiplication est<br/>
1, pas 0. Avec produit = 0, le résultat reste<br/>
toujours 0 quel que soit n. Bug classique<br/>
d'initialisation.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q28 : Récursivité croisée</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>On définit deux fonctions qui s'appellent mutuellement :<br/>
``<br/>
def pair(n):<br/>
    if n == 0:<br/>
        return True<br/>
    return impair(n - 1)</p>
<p>def impair(n):<br/>
    if n == 0:<br/>
        return False<br/>
    return pair(n - 1)<br/>
<code><br/>
Comment qualifier cette construction et que renvoie<br/>
pair(7)</code> ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>La récursivité croisée se prête à des problèmes où<br/>
plusieurs cas se répondent (analyseurs syntaxiques,<br/>
automates à plusieurs états, alternances). En pratique,<br/>
on peut souvent la réduire à une récursivité simple<br/>
avec un paramètre supplémentaire (ici, on pourrait<br/>
écrire parite(n, est_pair) qui alterne).</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>C'est de la récursivité croisée (ou mutuelle) :<br/>
deux fonctions s'appellent l'une l'autre, formant<br/>
ensemble un schéma récursif. pair(7) renvoie False</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : la récursivité croisée est un<br/>
schéma où plusieurs fonctions se définissent<br/>
mutuellement. Ici, en décrémentant n à chaque<br/>
appel et en alternant pair/impair, on aboutit à<br/>
pair(0) = True ou impair(0) = False selon la<br/>
parité de n. Pour n = 7, on alterne sept fois<br/>
et on arrive à impair(0), donc le résultat est<br/>
False.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Cette construction est invalide en Python ; le<br/>
programme plante immédiatement</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : Python autorise parfaitement deux fonctions<br/>
à s'appeler mutuellement, à condition qu'elles soient<br/>
toutes les deux définies au moment où on les appelle.<br/>
Aucune erreur n'est levée à la définition.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>C'est une récursivité terminale, et pair(7)<br/>
renvoie True</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur de qualification et de résultat : la<br/>
récursivité terminale désigne le fait que l'appel<br/>
récursif soit la dernière opération de la fonction<br/>
(ce qui est vrai ici, mais pas la caractéristique<br/>
principale). La caractéristique dominante est qu'il<br/>
y a deux fonctions s'appelant mutuellement. Et<br/>
pair(7) renvoie False.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>C'est une simple récursivité double, et pair(7)<br/>
renvoie True</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la récursivité double désigne deux appels<br/>
d'une même fonction, comme dans Fibonacci. Ici, on<br/>
a deux fonctions distinctes qui s'appellent<br/>
mutuellement, ce qui s'appelle récursivité<br/>
<strong>croisée</strong>. De plus, 7 est impair, donc<br/>
pair(7) renvoie False, pas True.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q29 : Récursivité avec accumulateur</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Voici deux écritures de la fonction qui calcule<br/>
la somme des entiers de 1 à n :</p>
<p>``<br/>
# Version A<br/>
def somme_a(n):<br/>
    if n == 0:<br/>
        return 0<br/>
    return n + somme_a(n - 1)</p>
<p># Version B<br/>
def somme_b(n, acc=0):<br/>
    if n == 0:<br/>
        return acc<br/>
    return somme_b(n - 1, acc + n)<br/>
``</p>
<p>Quelle est la <strong>différence essentielle</strong> entre<br/>
les deux versions ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Le motif accumulateur est très utilisé en<br/>
programmation fonctionnelle. Il consiste à<br/>
transporter le résultat partiel comme<br/>
paramètre, ce qui évite de laisser des<br/>
opérations en attente. Python ne fait<br/>
aucune optimisation de récursivité<br/>
terminale (volonté de Guido van Rossum<br/>
pour préserver la lisibilité des traces<br/>
d'erreur), mais l'écriture reste utile<br/>
pédagogiquement et pour porter du code<br/>
vers d'autres langages.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La version B utilise plus de mémoire</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est précisément l'inverse en<br/>
principe. Avec une optimisation de<br/>
récursivité terminale, la version B<br/>
peut s'exécuter avec une pile de taille<br/>
constante. Sans cette optimisation (cas<br/>
de Python), les deux versions consomment<br/>
la pile de manière comparable.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La version A est plus rapide à l'exécution</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : les deux versions ont la même<br/>
complexité asymptotique O(n) en temps.<br/>
La différence porte sur la <strong>structure</strong><br/>
des appels et la <strong>consommation de pile</strong>,<br/>
pas sur la rapidité.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>La version B donne un résultat différent<br/>
de la version A</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : les deux fonctions calculent<br/>
rigoureusement la même valeur,<br/>
1 + 2 + \ldots + n = n(n+1)/2. Vérifier<br/>
sur un exemple concret : pour n = 3,<br/>
les deux versions renvoient 6.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>La version A laisse une opération en<br/>
attente après chaque appel récursif (le<br/>
n + ...), tandis que la version B passe<br/>
le résultat partiel par un accumulateur<br/>
et n'a aucune opération en attente. La<br/>
version B est dite récursive terminale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : dans la version A, après<br/>
le retour de somme_a(n - 1), il reste<br/>
encore une addition à effectuer. Tous les<br/>
contextes intermédiaires doivent donc<br/>
rester sur la pile d'appels. Dans la<br/>
version B, l'accumulateur transporte le<br/>
résultat partiel comme paramètre de<br/>
l'appel suivant, sans rien laisser en<br/>
attente. Certains compilateurs (Scheme,<br/>
Haskell, OCaml) optimisent<br/>
automatiquement la version B en boucle,<br/>
évitant le débordement de pile pour de<br/>
grandes valeurs de n.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q30 : Trace de la pile d'appels</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Lors de l'appel factorielle(4) (avec la<br/>
définition récursive standard), combien de<br/>
cadres d'exécution sont <strong>simultanément</strong><br/>
empilés au moment où le cas de base est<br/>
atteint ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>C'est précisément cette accumulation de<br/>
cadres qui provoque RecursionError pour<br/>
les récursions très profondes. La pile<br/>
d'appels par défaut en Python a une taille<br/>
d'environ 1 000 cadres<br/>
(paramétrable avec<br/>
sys.setrecursionlimit, mais déconseillé).<br/>
Pour des problèmes nécessitant une<br/>
profondeur supérieure, il faut soit<br/>
utiliser une version itérative, soit<br/>
transformer la récursion en récursivité<br/>
terminale puis en boucle.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>5 cadres (pour factorielle(4), factorielle(3), factorielle(2), factorielle(1), factorielle(0))</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : chaque appel récursif<br/>
empile un nouveau cadre <strong>avant</strong> que le<br/>
précédent ne se termine. Au moment où<br/>
factorielle(0) atteint le cas de base,<br/>
tous les cadres précédents sont encore<br/>
en attente, car ils ont laissé<br/>
l'opération n * factorielle(n - 1) à<br/>
finir. Les cadres se dépilent ensuite<br/>
en sens inverse, en multipliant à chaque<br/>
étape. Pour n = 1000, on aurait 1001<br/>
cadres simultanés, ce qui dépasse la<br/>
limite par défaut de Python.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>1 cadre (Python optimise les appels récursifs)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : Python ne fait <strong>pas</strong> d'optimisation<br/>
de récursivité terminale, contrairement à<br/>
d'autres langages. Et même si la version<br/>
était terminale, la fonction factorielle<br/>
standard ne l'est pas (il y a une<br/>
multiplication en attente après chaque<br/>
appel récursif).</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>4 cadres</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : on oublie le cadre de<br/>
factorielle(0), le cas de base. Au<br/>
moment précis où il s'exécute, il est<br/>
bien sur la pile, en plus des cadres<br/>
des quatre appels précédents.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>2 cadres (le cadre courant et celui de la fonction qui a fait l'appel)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : tous les cadres récursifs sont<br/>
empilés simultanément, pas seulement<br/>
deux. Chaque appel récursif est lui-même<br/>
une « fonction qui a fait un appel »,<br/>
ce qui empile un nouveau cadre.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q31 : Fonction d'Ackermann</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>La fonction d'Ackermann est définie par :<br/>
`<code><br/>
A(0, n) = n + 1<br/>
A(m, 0) = A(m - 1, 1) si m &gt; 0<br/>
A(m, n) = A(m - 1, A(m, n - 1)) si m &gt; 0 et n &gt; 0<br/>
</code>`<br/>
Quelle est sa caractéristique remarquable ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Ackermann illustre la <strong>hiérarchie des<br/>
fonctions calculables</strong> : addition (rapide),<br/>
multiplication (=addition itérée),<br/>
exponentiation (=multiplication itérée),<br/>
tétration (=exponentiation itérée), et<br/>
ainsi de suite. Ackermann généralise ces<br/>
opérations à un niveau d'imbrication<br/>
arbitraire, ce qui la place hors d'atteinte<br/>
des récursions primitives.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Elle croît extrêmement vite : par exemple<br/>
A(4, 2) est un nombre comportant des<br/>
milliers de chiffres. Elle est calculable<br/>
mais n'est pas primitive récursive,<br/>
c'est-à-dire qu'elle ne peut pas être<br/>
obtenue par les schémas de récurrence<br/>
simples (sans appels imbriqués)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : la fonction d'Ackermann<br/>
a une importance théorique majeure en<br/>
calculabilité. Elle illustre que la<br/>
récursivité générale est strictement<br/>
plus puissante que la récurrence<br/>
primitive (boucles bornées). Au niveau<br/>
pédagogique, elle est un excellent<br/>
exemple de récursivité <strong>doublement<br/>
imbriquée</strong> : l'argument du second appel<br/>
dépend lui-même d'un appel récursif.<br/>
Quelques valeurs : A(0, 0) = 1,<br/>
A(1, 1) = 3, A(2, 2) = 7,<br/>
A(3, 3) = 61, A(4, 0) = 13,<br/>
A(4, 1) = 65 533.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle a une complexité polynomiale</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : sa complexité dépasse toute<br/>
fonction polynomiale, exponentielle, ou<br/>
tour d'exponentielles. C'est précisément<br/>
ce qui la rend remarquable.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle ne se calcule jamais (récursion<br/>
infinie)</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la fonction d'Ackermann termine<br/>
toujours, c'est-à-dire qu'elle est<br/>
<strong>totale</strong> sur ses entiers naturels. Mais<br/>
le temps de calcul peut être<br/>
astronomique pour m ≥ 4.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Elle calcule simplement la somme m + n</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la fonction d'Ackermann croît<br/>
beaucoup plus vite que l'addition, et<br/>
plus vite que la multiplication, et<br/>
plus vite que l'exponentiation, et plus<br/>
vite que toute itération finie de ces<br/>
opérations.</p>]]></text>
    </feedback>
  </answer>
</question>

<question type="multichoice">
  <name>
    <text>Récursivité — Q32 : Fibonacci itératif vs récursif</text>
  </name>
  <questiontext format="html">
    <text><![CDATA[<p>Pour calculer \mathrm{fib}(50), le temps<br/>
d'exécution sur un PC moderne est :</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cet exemple est l'argument le plus<br/>
pédagogique pour expliquer la mémoïsation.<br/>
Avec une mémoïsation simple, on retombe en<br/>
O(n) tout en gardant la lisibilité de la<br/>
version récursive. C'est le compromis<br/>
qu'offre la programmation dynamique :<br/>
garder la clarté de la formulation<br/>
récursive, sans en payer le coût<br/>
exponentiel.</p>]]></text>
  </generalfeedback>
  <defaultgrade>1.0</defaultgrade>
  <penalty>0.0</penalty>
  <hidden>0</hidden>
  <single>true</single>
  <shuffleanswers>true</shuffleanswers>
  <answernumbering>abc</answernumbering>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les deux versions sont instantanées</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : seule la version itérative est<br/>
instantanée. La version récursive naïve<br/>
devient impraticable bien avant n = 50.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les deux versions plantent par<br/>
dépassement de pile</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la version itérative ne<br/>
consomme pas la pile (juste deux<br/>
variables). La version récursive a une<br/>
profondeur d'appels de n = 50, ce qui<br/>
est largement sous la limite Python<br/>
(∼ 1 000). Le problème de la<br/>
version récursive est le <strong>temps</strong>, pas<br/>
la pile.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p>Quelques millisecondes pour la version<br/>
itérative ; plusieurs dizaines de<br/>
minutes, voire plus, pour la version<br/>
récursive naïve</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse. La version itérative est<br/>
en O(n) : pour n = 50, c'est environ<br/>
50 opérations, donc quelques<br/>
microsecondes. La version récursive<br/>
naïve est en O(φⁿ) avec<br/>
\varphi \approx 1{,}618 : pour n = 50, c'est environ<br/>
\varphi^{50} \approx 2{,}9 \cdot<br/>
10^{10} appels. À environ 10⁷ appels<br/>
par seconde en Python, cela représente<br/>
environ 50 minutes.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>Les deux versions sont équivalentes en<br/>
temps</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la différence est colossale,<br/>
plusieurs ordres de grandeur. C'est<br/>
l'exemple classique pour motiver la<br/>
mémoïsation ou la version itérative.</p>]]></text>
    </feedback>
  </answer>
</question>

</quiz>
