<?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 <code>factorielle(n)</code>). 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 <code>while</code> 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 <code>None</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : <code>None</code> est la valeur renvoyée par défaut quand une<br/>
fonction n'a pas d'instruction <code>return</code>. 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 <code>RecursionError</code></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 <code>f</code> 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/>
<code>RecursionError</code>.</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>
<pre><code>def f(n):
    if n == 0:
        return 1
    return 2 * f(n - 1)</code></pre>
<p>Que renvoie l'appel <code>f(4)</code> ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette fonction calcule $2^n$. La récurrence $f(n) = 2 \cdot f(n-1)$<br/>
avec $f(0) = 1$ donne immédiatement $f(n) = 2^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>$32$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : c'est $f(5) = 2^5$. 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^4$, 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^3$. 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 <code>factorielle</code> ainsi :</p>
<pre><code>def factorielle(n):
    if n &lt;= 1:
        return 1
    return n * factorielle(n - 1)</code></pre>
<p>Quel est le rôle de la condition <code>if n &lt;= 1: return 1</code> ?</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 <code>RecursionError</code>. 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 <code>n</code> 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 \leq 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 <code>n</code> est négatif</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la condition couvre $n \leq 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>
<pre><code>def pgcd(a, b):
    if b == 0:
        return a
    return pgcd(b, a % b)</code></pre>]]></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><code>a == 0</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : la condition d'arrêt teste <code>b</code>, pas <code>a</code>.<br/>
L'algorithme d'Euclide réduit progressivement <code>b</code> vers $0$.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="100" format="html">
    <text><![CDATA[<p><code>b == 0</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : quand <code>b</code> vaut $0$, la fonction renvoie <code>a</code><br/>
directement, sans nouvel appel. C'est le cas de base.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>a == b</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce cas n'est pas géré explicitement. Dans cet<br/>
algorithme, l'égalité <code>a == b</code> mène à <code>pgcd(b, 0)</code> au prochain<br/>
appel, qui déclenche le vrai cas de base.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>a % b == 0</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : ce n'est pas la condition utilisée. Si <code>a % b == 0</code>,<br/>
l'appel récursif suivant sera <code>pgcd(b, 0)</code>, 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/>
<code>factorielle</code>).</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 <code>pair(n)</code> et<br/>
<code>impair(n)</code>), 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/>
<code>factorielle(n)</code>, le variant est $n$. Pour une fonction sur<br/>
<code>L[1:]</code>, c'est <code>len(L)</code>.</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>
<pre><code>def compte(chaine):
    if chaine == "":
        return 0
    return 1 + compte(chaine[1:])</code></pre>]]></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><code>chaine == ""</code></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><code>1 + compte(chaine[1:])</code></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 <code>if chaine == "": return 0</code> arrête bien<br/>
la récursion sans nouvel appel.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>chaine[1:]</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : <code>chaine[1:]</code> 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_n$ dépend uniquement de $u_{n-1}$). 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_n = f(u_{n-1}, u_{n-2})$</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_0$ et $u_{n} = f(u_{n-1})$</p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on a un cas de base ($u_0$) et un appel<br/>
récursif (calcul de $u_n$ à partir de $u_{n-1}$). 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>
<pre><code>def somme(L):
    if L == []:
        return 0
    return L[0] + somme(L[1:])</code></pre>
<p>Que renvoie <code>somme([3, 5, 2])</code> ?</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>
<pre><code>def nb_chiffres(n):
    if n &lt; 10:
        return 1
    return 1 + nb_chiffres(n // 10)</code></pre>
<p>Que renvoie <code>nb_chiffres(4073)</code> ?</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 <code>c</code> dans une chaîne <code>s</code>, par quoi faut-il<br/>
remplacer les pointillés ?</p>
<pre><code>def occurrences(s, c):
    if s == "":
        return ...
    if s[0] == c:
        return 1 + occurrences(s[1:], c)
    return occurrences(s[1:], c)</code></pre>]]></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><code>None</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : <code>None</code> 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><code>c</code></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>
<pre><code>def maximum(L):
    if ...:
        return L[0]
    ...</code></pre>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Une fois le cas de base posé (<code>len(L) == 1</code>), l'appel récursif<br/>
compare le premier élément au maximum du reste de la liste :<br/>
<code>m = maximum(L[1:])</code> puis renvoie <code>L[0] if L[0] &gt; m else m</code>.</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>L[0] &gt; L[-1]</code></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><code>L == []</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : sur une liste vide, <code>L[0]</code> provoquerait une erreur<br/>
(<code>IndexError</code>). Le cas de base doit garantir que <code>L[0]</code><br/>
existe.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p><code>len(L) == 2</code></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><code>len(L) == 1</code></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 <code>miroir</code><br/>
ci-dessous, qui doit renvoyer la liste inversée ?</p>
<pre><code>def miroir(L):
    if L == []:
        return []
    return ...</code></pre>]]></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 <code>+</code>).</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><code>miroir(L[1:]) + [L[0]]</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on inverse récursivement le reste de la<br/>
liste, puis on place <code>L[0]</code> à 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><code>miroir(L[0]) + miroir(L[1:])</code></p>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : <code>L[0]</code> 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><code>miroir(L)[1:]</code></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><code>[L[0]] + miroir(L[1:])</code></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 <code>L[0]</code> 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>
<pre><code>def population(n):
    if n == 0:
        return 1
    return 2 * population(n - 1)</code></pre>
<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^{20} \approx 10^6$ 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^n$, 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 \cdot 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^{11}$, 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>
<pre><code>def cherche(elt, arbre):
    if arbre is None:
        return False
    if arbre.valeur == elt:
        return True
    return cherche(elt, arbre.gauche) or cherche(elt, arbre.droite)</code></pre>
<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 <code>or</code> court-circuite l'appel droit dès que l'appel gauche<br/>
renvoie <code>True</code>, 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/>
(<code>cherche(elt, arbre.gauche)</code> et<br/>
<code>cherche(elt, arbre.droite)</code>). 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/>
<code>RecursionError</code> 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/>
<code>sys.setrecursionlimit()</code>, 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 <code>fibonacci(n)</code> ?</p>
<pre><code>def fibonacci(n):
    if n &lt;= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)</code></pre>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p><code>fibonacci(35)</code> 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^2)$</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^n)$</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^n$ (plus précisément $\varphi^n$ avec<br/>
$\varphi$ 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^n)$ en $O(n)$. Python propose un décorateur<br/>
<code>@functools.lru_cache</code> 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^n - 1$, donné par la récurrence<br/>
$T(n) = 2 \cdot 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^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^3 - 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^n - 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 \cdot 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^n$. 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/>
<code>RecursionError</code>). 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 $\frac{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 ($\sim<br/>
1000$ appels). Une boucle <code>for</code> 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>
<pre><code>def f(n):
    if n == 0:
        return 0
    if n == 1:
        return 1
    return f(n - 1) + f(n - 2) + 1</code></pre>
<p>Combien d'<strong>appels récursifs au total</strong> sont effectués pour<br/>
calculer <code>f(4)</code> (en comptant l'appel initial) ?</p>]]></text>
  </questiontext>
  <generalfeedback format="html">
    <text><![CDATA[<p>Cette fonction calcule $f(n) = F_{n+1} - 1$, où $F_n$ 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 à <code>f</code> 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 : <code>f(4)</code> appelle <code>f(3)</code> et <code>f(2)</code>. <code>f(3)</code><br/>
appelle <code>f(2)</code> et <code>f(1)</code>. <code>f(2)</code> appelle <code>f(1)</code> et <code>f(0)</code>.<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^4$ 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 <code>fibonacci(20)</code> 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^n)$ à<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^{20}$ (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[<pre><code>def f(n):
    if n &lt;= 1:
        return n
    return f(n - 1) + f(n - 2)</code></pre>]]></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[<pre><code>def f(n):
    if n == 0:
        return None
    print(n)
    return f(n - 1) + 1</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : après l'appel récursif <code>f(n - 1)</code>, on<br/>
ajoute <code>1</code>. 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[<pre><code>def f(n):
    if n == 0:
        return 1
    return n * f(n - 1)</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : après l'appel récursif <code>f(n - 1)</code>, on<br/>
effectue encore une multiplication par <code>n</code>. 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[<pre><code>def f(n, acc):
    if n == 0:
        return acc
    return f(n - 1, n * acc)</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : l'appel récursif <code>f(n - 1, n * acc)</code><br/>
est la dernière opération de la fonction (la<br/>
multiplication par <code>n</code> 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 <code>factorielle</code> 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[<pre><code>def factorielle(n):
    if n == 0:
        return 1
    return n * factorielle(n - 1)</code></pre>]]></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[<pre><code>def factorielle(n):
    return n ** n</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur grossière : $n^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[<pre><code>def factorielle(n):
    produit = 1
    for i in range(1, n + 1):
        produit = produit * i
    return produit</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Bonne réponse : on remplace les appels récursifs par<br/>
une boucle <code>for</code> 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 <code>RecursionError</code> sur des grandes valeurs de <code>n</code>.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<pre><code>def factorielle(n):
    produit = 0
    for i in range(1, n + 1):
        produit = produit * i
    return produit</code></pre>]]></text>
    <feedback format="html">
      <text><![CDATA[<p>Erreur : l'élément neutre de la multiplication est<br/>
$1$, pas $0$. Avec <code>produit = 0</code>, le résultat reste<br/>
toujours $0$ quel que soit <code>n</code>. 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/>
<pre><code>def pair(n):
    if n == 0:
        return True
    return impair(n - 1)

def impair(n):
    if n == 0:
        return False
    return pair(n - 1)</code></pre><br/>
Comment qualifier cette construction et que renvoie<br/>
<code>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 <code>parite(n, est_pair)</code> 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. <code>pair(7)</code> renvoie <code>False</code></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 <code>n</code> à chaque<br/>
appel et en alternant pair/impair, on aboutit à<br/>
<code>pair(0) = True</code> ou <code>impair(0) = False</code> selon la<br/>
parité de <code>n</code>. Pour <code>n = 7</code>, on alterne sept fois<br/>
et on arrive à <code>impair(0)</code>, donc le résultat est<br/>
<code>False</code>.</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 <code>pair(7)</code><br/>
renvoie <code>True</code></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/>
<code>pair(7)</code> renvoie <code>False</code>.</p>]]></text>
    </feedback>
  </answer>
  <answer fraction="0" format="html">
    <text><![CDATA[<p>C'est une simple récursivité double, et <code>pair(7)</code><br/>
renvoie <code>True</code></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/>
<code>pair(7)</code> renvoie <code>False</code>, pas <code>True</code>.</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>
<pre><code># Version A
def somme_a(n):
    if n == 0:
        return 0
    return n + somme_a(n - 1)

# Version B
def somme_b(n, acc=0):
    if n == 0:
        return acc
    return somme_b(n - 1, acc + n)</code></pre>
<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/>
<code>n + ...</code>), 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 <code>somme_a(n - 1)</code>, 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 <code>n</code>.</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 <code>factorielle(4)</code> (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 <code>RecursionError</code> 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/>
<code>sys.setrecursionlimit</code>, 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 <code>factorielle(4)</code>, <code>factorielle(3)</code>, <code>factorielle(2)</code>, <code>factorielle(1)</code>, <code>factorielle(0)</code>)</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/>
<code>factorielle(0)</code> 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 <code>n * factorielle(n - 1)</code> à<br/>
finir. Les cadres se dépilent ensuite<br/>
en sens inverse, en multipliant à chaque<br/>
étape. Pour <code>n = 1000</code>, 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 <code>factorielle</code><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/>
<code>factorielle(0)</code>, 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/>
<pre><code>A(0, n) = n + 1
A(m, 0) = A(m - 1, 1) si m &gt; 0
A(m, n) = A(m - 1, A(m, n - 1)) si m &gt; 0 et n &gt; 0</code></pre><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 \geq 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 =<br/>
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/>
($\sim 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(\varphi^n)$ avec<br/>
$\varphi \approx 1{,}618$ : pour $n =<br/>
50$, c'est environ<br/>
$\varphi^{50} \approx 2{,}9 \cdot<br/>
10^{10}$ appels. À environ $10^7$ 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>
