Mathématiques pour l'informatique 201-N11 • Cégep à Montréal

Exercices corrigés : récurrence et récursivité (201-N11)

Voici la série d'exercices corrigés de mathématiques pour l'informatique 201-N11 sur la récurrence et la récursivité : le raisonnement qui démontre une infinité d'énoncés, sa traduction en programmes, et l'analyse du coût qui en résulte.

Le fil de la série tient en une phrase : une démonstration par récurrence a DEUX étapes, et en oublier une ne donne pas une preuve incomplète mais une preuve fausse. Le même souci de rigueur se retrouve du côté des programmes, où un cas de base manquant transforme une fonction en boucle infinie.

La partie A construit le raisonnement, ses variantes forte et à deux crans, puis sa traduction en Python. La partie B analyse : résolution exacte d'une récurrence linéaire, coût de la dichotomie et du tri fusion, tours de Hanoï, et enfin le critère qui dit si un algorithme lent se répare par mémoïsation ou pas du tout. Tous les corrigés sont sur la page, avec le code Python.

Série autocorrigée Tape tes réponses sous chaque question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points.

Ce chapitre fait partie de Mathématiques pour l'informatique, 201-N11
Avant de commencer Fiche de révision : les pièges et la méthode de ce chapitre

Avant ce chapitre

Ces notions sont supposées acquises ici. Si le premier exercice résiste, le blocage vient presque toujours de l'une d'elles, pas du chapitre lui-même.

Remonter plus loin : la chaîne complète (2 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Logique booléenne et mathématique
  2. 2Théorie des ensembles et relations

Rappel de cours

  • RÉCURRENCE : (1) INITIALISATION, P(n0)P(n_{0}) est vraie ; (2) HÉRÉDITÉ, pour tout kn0k\geq n_{0}, P(k)P(k) entraîne P(k+1)P(k+1). Alors P(n)P(n) est vraie pour tout nn0n\geq n_{0}. L'hérédité est une IMPLICATION : on ne suppose jamais que P(k)P(k) est vraie.
  • LES DEUX ÉCHECS TYPES : hérédité sans initialisation, comme « n=n+1n=n+1 », dont l'hérédité est valide et la conclusion fausse ; vérifications sans hérédité, comme « n2+n+41n^{2}+n+41 est premier », vrai jusqu'à 39 et faux en 40.
  • RÉCURRENCE FORTE : l'hypothèse porte sur TOUS les rangs jusqu'à kk, ce qui sert dès qu'on décompose un objet en morceaux de tailles inconnues. RÉCURRENCE À DEUX CRANS : il faut DEUX initialisations, autant que de rangs antérieurs dans la relation.
  • FONCTION RÉCURSIVE : elle termine si elle possède un CAS DE BASE et si chaque appel porte sur un argument strictement plus petit qui finit par l'atteindre. La PILE D'APPELS conserve un enregistrement par appel en cours, d'où la limite d'environ mille appels imbriqués en Python.
  • COÛTS USUELS : T(n)=T(n2)+1T(n)=T\left(\frac{n}{2}\right)+1 donne log2n\log_{2}n, la dichotomie. T(n)=2T(n2)+nT(n)=2T\left(\frac{n}{2}\right)+n donne nlog2nn\log_{2}n, le tri fusion. T(n)=2T(n1)+1T(n)=2T(n-1)+1 donne 2n12^{n}-1, les tours de Hanoï. T(n)=T(n1)+T(n2)+1T(n)=T(n-1)+T(n-2)+1 donne 2Fn+112F_{n+1}-1, Fibonacci naïf.
  • MÉMOÏSATION : quand les sous-problèmes se RECOUVRENT, retenir les valeurs déjà calculées ramène un coût exponentiel à un coût linéaire. Quand ils ne se recouvrent pas, comme dans Hanoï, le coût est intrinsèque au problème et rien ne l'améliorera.
  • ÉQUATION CARACTÉRISTIQUE : pour un+2=aun+1+bunu_{n+2}=au_{n+1}+bu_{n}, on résout r2=ar+br^{2}=ar+b et l'on écrit un=Ar1n+Br2nu_{n}=A r_{1}^{n}+B r_{2}^{n}, les conditions initiales fixant AA et BB. Sur Fibonacci, cela donne la formule de Binet.

Partie A : les bases (/50)

Exercice 1 : Le raisonnement par récurrence : deux étapes, pas une

La récurrence est le seul outil qui démontre une infinité d'énoncés en deux lignes. Le fil de la série : une démonstration par récurrence a DEUX étapes, et laisser tomber l'une ou l'autre ne produit pas une démonstration incomplète, elle produit une démonstration fausse.

P(0)P(1)P(2)P(3)P(4)P(5)P(6)P(7)on poussechaque domino fait tomber le suivant : P(k) donne P(k+1)initialisation plus heredite, donc tous tombent
  • a) Énoncez le principe de récurrence en nommant précisément ses deux étapes.
  • b) Démontrez par récurrence que 1+2++n=n(n+1)21+2+\dots+n=\frac{n(n+1)}{2} pour tout n1n\geq 1.
  • c) Que se passe-t-il si l'on oublie l'INITIALISATION ? Illustrez avec la propriété « n=n+1n=n+1 ».
  • d) Que se passe-t-il si l'on oublie l'HÉRÉDITÉ ? Illustrez avec « n2+n+41n^{2}+n+41 est premier ».
  • e) Sur la figure, dites ce que représentent le geste initial et la chaîne de dominos.

Tape tes réponses, la page te dit juste ou faux 0/12

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Initialisation en n0n_{0}, puis hérédité : pour tout kn0k \ge n_{0}, P(k)P(k) entraîne P(k+1)P(k+1)
  • b) Vraie en n=1n=1, et k(k+1)2+(k+1)=(k+1)(k+2)2\frac{k(k+1)}{2}+(k+1) = \frac{(k+1)(k+2)}{2}
  • c) L'hérédité de « n=n+1n=n+1 » est valide et la propriété est fausse partout : sans premier domino, rien ne tombe
  • d) n2+n+41n^{2}+n+41 est premier de n=0n=0 à n=39n=39, puis 402+40+41=1681=41240^{2}+40+41 = 1681 = 41^{2}
  • e) Le geste initial est l'INITIALISATION, la chaîne est l'HÉRÉDITÉ

a) Soit P(n)P(n) une propriété dépendant d'un entier nn. Si (1) INITIALISATION : P(n0)P(n_{0}) est vraie pour un premier rang n0n_{0} ; et (2) HÉRÉDITÉ : pour tout kn0k\geq n_{0}, P(k)P(k) vraie entraîne P(k+1)P(k+1) vraie ; alors P(n)P(n) est vraie pour tout nn0n\geq n_{0}. L'hérédité est une implication, pas une affirmation : on ne prétend jamais que P(k)P(k) est vraie, on suppose qu'elle l'est.

b) INITIALISATION en n=1n=1 : le membre de gauche vaut 1, celui de droite 1×22=1\frac{1\times 2}{2}=1 ✓. HÉRÉDITÉ : supposons 1+2++k=k(k+1)21+2+\dots+k=\frac{k(k+1)}{2} pour un certain k1k\geq 1. Alors 1+2++k+(k+1)=k(k+1)2+(k+1)=(k+1)(k2+1)=(k+1)(k+2)21+2+\dots+k+(k+1)=\frac{k(k+1)}{2}+(k+1)=(k+1)\left(\frac{k}{2}+1\right)=\frac{(k+1)(k+2)}{2}, qui est bien la formule au rang k+1k+1 ✓. Contrôle numérique en n=10n=10 : 10×112=55\frac{10\times 11}{2}=55, et la somme vaut effectivement 55.

c) Sans initialisation, l'hérédité seule ne prouve rien. Prenons P(n)P(n) : « n=n+1n=n+1 ». L'hérédité est PARFAITEMENT VRAIE : si k=k+1k=k+1, alors en ajoutant 1 aux deux membres on obtient k+1=k+2k+1=k+2, donc P(k+1)P(k+1). La chaîne d'implications est irréprochable, et pourtant P(n)P(n) est fausse pour tout nn, faute d'un premier domino à pousser. C'est le contre-exemple à retenir, parce qu'il montre qu'une hérédité valide n'a aucune valeur toute seule.

d) Sans hérédité, vérifier des cas ne prouve rien non plus. La propriété « n2+n+41n^{2}+n+41 est premier » est vraie pour n=0n=0, où l'on obtient 41, pour n=1n=1, où l'on obtient 43, et en fait pour tous les entiers de 0 à 39. Elle est FAUSSE en n=40n=40, car 402+40+41=1681=41240^{2}+40+41=1681=41^{2}. Quarante vérifications successives ne valent pas une démonstration : l'hérédité est ce qui remplace une infinité de vérifications.

e) Le geste initial, la main qui pousse le premier domino, représente l'INITIALISATION. La chaîne, c'est-à-dire le fait que chaque domino soit assez près du suivant pour le faire tomber, représente l'HÉRÉDITÉ. Sans le geste, la chaîne reste debout ; sans la chaîne, seul le premier tombe. Il faut donc les deux, et l'image dit aussi pourquoi : chacun des deux ingrédients est inutile sans l'autre.

Exercice 2 : Trois familles de récurrences : sommes, inégalités, divisibilité

Le mécanisme est toujours le même, mais le geste de l'hérédité change selon la famille : on ajoute un terme, on majore, ou l'on fait apparaître un multiple.

  • a) Démontrez que 12+22++n2=n(n+1)(2n+1)61^{2}+2^{2}+\dots+n^{2}=\frac{n(n+1)(2n+1)}{6} pour tout n1n\geq 1.
  • b) Démontrez que 2n>n22^{n}>n^{2} pour tout n5n\geq 5. Vérifiez d'abord pourquoi le rang de départ n'est pas 1.
  • c) Démontrez que 32n2n3^{2n}-2^{n} est divisible par 7 pour tout n1n\geq 1.
  • d) Dans chacune des trois démonstrations, dites en une phrase où l'hypothèse de récurrence a été utilisée.
  • e) Pourquoi une démonstration par récurrence qui n'utilise jamais l'hypothèse est-elle suspecte ?

Tape tes réponses, la page te dit juste ou faux 0/14

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Vraie en n=1n=1, et (k+1)(2k2+7k+6)6=(k+1)(k+2)(2k+3)6\frac{(k+1)(2k^{2}+7k+6)}{6} = \frac{(k+1)(k+2)(2k+3)}{6}
  • b) Le rang 5 est nécessaire, 25=32>252^{5}=32 > 25, et l'hérédité passe par 2k2(k+1)22k^{2} \ge (k+1)^{2}, vrai dès k3k \ge 3
  • c) 32(k+1)2k+1=7(9m+2k)3^{2(k+1)}-2^{k+1} = 7(9m+2^{k}), et le contrôle donne 7298=721=7×103729-8 = 721 = 7 \times 103
  • d) Elle remplace la somme par sa forme close, fournit la minoration à multiplier par 2, fournit l'écriture 7m7m à mettre en facteur
  • e) Parce qu'on aurait alors démontré P(k+1)P(k+1) directement : la récurrence était inutile, et l'erreur probable

a) INITIALISATION en n=1n=1 : à gauche 1, à droite 1×2×36=1\frac{1\times 2\times 3}{6}=1 ✓. HÉRÉDITÉ : en supposant la formule au rang kk, la somme au rang k+1k+1 vaut k(k+1)(2k+1)6+(k+1)2=(k+1)[k(2k+1)+6(k+1)]6=(k+1)(2k2+7k+6)6=(k+1)(k+2)(2k+3)6\frac{k(k+1)(2k+1)}{6}+(k+1)^{2}=\frac{(k+1)\left[k(2k+1)+6(k+1)\right]}{6}=\frac{(k+1)\left(2k^{2}+7k+6\right)}{6}=\frac{(k+1)(k+2)(2k+3)}{6}, qui est la formule au rang k+1k+1 ✓. Le pas décisif est la factorisation de 2k2+7k+62k^{2}+7k+6 en (k+2)(2k+3)(k+2)(2k+3) : on la trouve en cherchant la forme attendue, jamais en développant au hasard. Contrôle en n=5n=5 : 1+4+9+16+25=551+4+9+16+25=55 et 5×6×116=55\frac{5\times 6\times 11}{6}=55 ✓.

b) Le rang 5 est nécessaire : en n=2n=2, 4>44>4 est faux ; en n=3n=3, 8>98>9 est faux ; en n=4n=4, 16>1616>16 est faux. En n=5n=5 : 32>2532>25 ✓, l'initialisation passe. HÉRÉDITÉ : supposons 2k>k22^{k}>k^{2} avec k5k\geq 5. Alors 2k+1=2×2k>2k22^{k+1}=2\times 2^{k}>2k^{2}. Il reste à montrer que 2k2(k+1)22k^{2}\geq(k+1)^{2}, c'est-à-dire k22k10k^{2}-2k-1\geq 0, ce qui est vrai dès k3k\geq 3 puisque k22k1=(k1)22k^{2}-2k-1=(k-1)^{2}-2. Donc 2k+1>(k+1)22^{k+1}>(k+1)^{2} ✓. Une inégalité par récurrence demande presque toujours cette étape supplémentaire de majoration.

c) INITIALISATION : 322=92=73^{2}-2=9-2=7, divisible par 7 ✓. HÉRÉDITÉ : supposons 32k2k=7m3^{2k}-2^{k}=7m pour un entier mm. Alors 32(k+1)2k+1=9×32k2×2k=9(32k2k)+9×2k2×2k=9×7m+7×2k=7(9m+2k)3^{2(k+1)}-2^{k+1}=9\times 3^{2k}-2\times 2^{k}=9\left(3^{2k}-2^{k}\right)+9\times 2^{k}-2\times 2^{k}=9\times 7m+7\times 2^{k}=7\left(9m+2^{k}\right), multiple de 7 ✓. L'astuce est de faire apparaître de force l'expression du rang kk en ajoutant et retranchant le terme manquant. Contrôle en n=3n=3 : 7298=721=7×103729-8=721=7\times 103 ✓.

d) SOMME : l'hypothèse remplace la somme des kk premiers termes par sa forme close, ce qui transforme une somme en expression algébrique. INÉGALITÉ : l'hypothèse fournit la minoration 2k>k22^{k}>k^{2} qu'on multiplie par 2. DIVISIBILITÉ : l'hypothèse fournit l'écriture 32k2k=7m3^{2k}-2^{k}=7m, que l'on met en facteur. Dans les trois cas, elle sert à REMPLACER une expression du rang kk par quelque chose de connu.

e) Parce que si l'hypothèse ne sert jamais, c'est que l'on a démontré P(k+1)P(k+1) directement, sans rien supposer : la récurrence n'était donc pas nécessaire, et il est bien plus probable qu'on ait commis une erreur, typiquement en supposant ce qu'il fallait démontrer. Le contrôle vaut d'être fait systématiquement : relire l'hérédité et pointer du doigt l'endroit exact où l'hypothèse a été employée. Si l'on n'en trouve pas, il y a un problème.

Exercice 3 : Récurrence forte et récurrence à deux crans

Certaines propriétés ne se déduisent pas du rang précédent seul : il faut deux rangs en arrière, ou tous les rangs à la fois. Le principe s'adapte, à condition d'ajuster aussi l'initialisation.

  • a) Énoncez le principe de récurrence FORTE et dites en quoi il diffère du principe ordinaire.
  • b) Démontrez par récurrence forte que tout entier n2n\geq 2 admet au moins un diviseur premier.
  • c) La suite de Fibonacci est définie par F0=0F_{0}=0, F1=1F_{1}=1 et Fn+2=Fn+1+FnF_{n+2}=F_{n+1}+F_{n}. Combien d'initialisations faut-il pour démontrer une propriété de cette suite, et pourquoi ?
  • d) Démontrez que Fn<2nF_{n}<2^{n} pour tout n0n\geq 0.
  • e) Une démonstration à deux crans initialisée sur un seul rang peut-elle échouer ? Donnez un exemple sur la suite définie par u0=1u_{0}=1, u1=3u_{1}=3 et un+2=3un+12unu_{n+2}=3u_{n+1}-2u_{n}.

Tape tes réponses, la page te dit juste ou faux 0/12

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) On suppose TOUS les rangs jusqu'à kk, et non le seul rang kk ; les deux principes sont équivalents
  • b) Si nn est premier, il se divise lui-même ; sinon n=abn=ab et un diviseur premier de aa divise nn
  • c) DEUX, aux rangs 0 et 1, puisque l'hérédité mobilise Fn+1F_{n+1} ET FnF_{n}
  • d) Fk+2<2k+1+2k=3×2k<2k+2F_{k+2} < 2^{k+1}+2^{k} = 3 \times 2^{k} < 2^{k+2}, et F10=55<1024F_{10} = 55 < 1024
  • e) Oui : avec une seule initialisation, on ne peut même pas produire u2u_{2}. Ici un=2n+11u_{n} = 2^{n+1}-1

a) RÉCURRENCE FORTE : si P(n0)P(n_{0}) est vraie, et si pour tout kk le fait que P(n0),P(n0+1),,P(k)P(n_{0}),P(n_{0}+1),\dots,P(k) soient TOUTES vraies entraîne P(k+1)P(k+1), alors P(n)P(n) est vraie pour tout nn0n\geq n_{0}. La différence tient à l'hypothèse : on ne dispose plus du seul rang kk mais de TOUS les rangs jusqu'à kk. Les deux principes sont en fait logiquement équivalents, mais la forme forte est bien plus commode dès qu'on décompose un objet en morceaux de tailles inconnues.

b) INITIALISATION : 2 est premier, il est donc son propre diviseur premier ✓. HÉRÉDITÉ FORTE : soit n3n\geq 3, et supposons la propriété vraie pour tous les entiers de 2 à n1n-1. Deux cas. Si nn est premier, il admet nn comme diviseur premier ✓. Sinon, nn s'écrit n=abn=ab avec 2a<n2\leq a<n, et l'hypothèse forte appliquée à aa fournit un diviseur premier pp de aa ; comme pp divise aa qui divise nn, pp divise nn ✓. On voit pourquoi la récurrence ordinaire ne suffirait pas : le facteur aa n'a aucune raison d'être égal à n1n-1.

c) Il en faut DEUX, aux rangs 0 et 1. La raison est que l'hérédité déduit le rang n+2n+2 des rangs n+1n+1 ET nn : pour démarrer, il faut donc disposer de deux valeurs consécutives déjà établies. Avec une seule initialisation, on ne pourrait jamais calculer F2F_{2}, faute de connaître l'un des deux termes dont il dépend.

d) INITIALISATIONS : F0=0<1=20F_{0}=0<1=2^{0} ✓ et F1=1<2=21F_{1}=1<2=2^{1} ✓. HÉRÉDITÉ : supposons Fk<2kF_{k}<2^{k} et Fk+1<2k+1F_{k+1}<2^{k+1}. Alors Fk+2=Fk+1+Fk<2k+1+2k=2k(2+1)=3×2k<4×2k=2k+2F_{k+2}=F_{k+1}+F_{k}<2^{k+1}+2^{k}=2^{k}\left(2+1\right)=3\times 2^{k}<4\times 2^{k}=2^{k+2} ✓. La majoration finale est généreuse, et c'est volontaire : on démontre ce qu'on demande, pas le meilleur résultat possible. Contrôle : F10=55<1024=210F_{10}=55<1024=2^{10} ✓.

e) Oui, elle échoue. Cette suite vérifie un=2n+11u_{n}=2^{n+1}-1 : on a u0=1u_{0}=1, u1=3u_{1}=3, u2=3×32×1=7u_{2}=3\times 3-2\times 1=7, u3=3×72×3=15u_{3}=3\times 7-2\times 3=15. Supposons qu'on veuille démontrer « unu_{n} est impair » en n'initialisant qu'au rang 0. L'hérédité un+2=3un+12unu_{n+2}=3u_{n+1}-2u_{n} demande un+1u_{n+1} ET unu_{n} ; à l'étape qui produit u2u_{2}, on ne dispose que de u0u_{0} et la démonstration s'arrête net. Ce n'est pas une subtilité de rédaction : le nombre d'initialisations doit toujours être égal au nombre de rangs antérieurs dont dépend la relation.

Exercice 4 : Des suites récurrentes aux fonctions récursives

Une définition par récurrence se traduit presque mot pour mot en une fonction qui s'appelle elle-même. Deux règles suffisent pour que le programme s'arrête : un cas de base, et un argument qui décroît.

def factorielle(n):
    if n == 0:
        return 1
    return n * factorielle(n - 1)

def factorielle_iter(n):
    resultat = 1
    for k in range(2, n + 1):
        resultat = resultat * k
    return resultat

def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)
  • a) Écrivez en Python la fonction factorielle en version récursive, puis en version itérative.
  • b) Quelles sont les deux conditions pour qu'une fonction récursive termine ? Donnez un exemple qui viole chacune d'elles.
  • c) Écrivez la fonction de Fibonacci en version récursive naïve.
  • d) Que se passe-t-il si l'on appelle cette fonction récursive de factorielle avec un argument négatif ? Comment corriger ?
  • e) Qu'est-ce que la pile d'appels, et pourquoi Python interrompt-il un programme après environ mille appels imbriqués ?

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) n!=n×(n1)!n! = n \times (n-1)! avec 0!=10! = 1 ; 5!=1205! = 120 et 10!=362880010! = 3\,628\,800 dans les deux versions
  • b) Un cas de base, et un argument strictement décroissant qui l'atteint
  • c) Deux cas de base et la relation Fn=Fn1+Fn2F_{n} = F_{n-1}+F_{n-2} : correcte, mais très lente
  • d) Le cas de base n'est jamais atteint : il faut une garde qui rejette les entrées négatives
  • e) La zone qui garde variables locales et adresses de retour ; la limite d'environ 1000 est un garde-fou

a) La version récursive traduit littéralement la définition n!=n×(n1)!n!=n\times(n-1)! avec 0!=10!=1. La version itérative accumule le produit dans une boucle. Les deux calculent la même chose ; la récursive est plus proche de la définition mathématique, l'itérative consomme une mémoire constante. Contrôle : 5!=1205!=120 et 10!=362880010!=3\,628\,800 dans les deux cas.

b) (1) Il faut un CAS DE BASE, c'est-à-dire au moins une valeur pour laquelle la fonction répond sans se rappeler. (2) Il faut que chaque appel récursif porte sur un argument STRICTEMENT PLUS PETIT, et qui finisse par atteindre le cas de base. Violation de la première : une fonction qui s'écrit uniquement f(n)=f(n1)f(n)=f(n-1) ne s'arrête jamais. Violation de la seconde : f(n)=f(n)+1f(n)=f(n)+1, ou plus insidieusement f(n)=f(n2)f(n)=f(n-2) appelée sur un nombre impair alors que le cas de base est f(0)f(0), qui manque toujours sa cible d'une unité.

c) La version naïve traduit la relation Fn=Fn1+Fn2F_{n}=F_{n-1}+F_{n-2} avec deux cas de base. Elle est correcte mais catastrophiquement lente, pour la raison examinée à l'exercice 7 : elle recalcule indéfiniment les mêmes valeurs.

d) Avec un argument négatif, le cas de base n=0n=0 n'est jamais atteint : les appels descendent vers 1-1, 2-2, et ainsi de suite, sans fin. Python finit par lever une erreur de dépassement de la profondeur maximale de récursion. CORRECTION : ajouter une garde au début de la fonction qui rejette les entrées négatives, par exemple en levant une exception. C'est l'illustration exacte de la seconde condition de la question b : l'argument décroît bien, mais il ne rencontre jamais le cas de base.

e) La PILE D'APPELS est la zone de mémoire où l'interpréteur conserve, pour chaque appel en cours, les variables locales et l'endroit où revenir une fois le calcul terminé. Une récursion de profondeur nn y empile nn enregistrements simultanés. Python fixe par défaut une limite d'environ mille appels imbriqués, non par manque de mémoire mais comme GARDE-FOU : elle transforme une récursion infinie, qui ferait planter le processus de façon opaque, en une exception lisible. On peut relever cette limite, mais c'est presque toujours le signe qu'il fallait écrire une boucle.

Exercice 5 : Le coût d'une récursion naïve

La fonction de Fibonacci récursive tient en trois lignes et devient inutilisable dès n=40n=40. Compter ses appels explique pourquoi, et suggère la correction.

543322121101010fib(5) sans memoire : 15 appels, dont fib(2) trois fois
memo = {}

def fib_memo(n):
    if n < 2:
        return n
    if n in memo:
        return memo[n]
    memo[n] = fib_memo(n - 1) + fib_memo(n - 2)
    return memo[n]
  • a) Sur l'arbre de la figure, comptez les appels engendrés par fib(5)\mathrm{fib}(5) et repérez combien de fois fib(2)\mathrm{fib}(2) est calculé.
  • b) On note C(n)C(n) le nombre total d'appels. Écrivez la relation vérifiée par CC, puis calculez C(0)C(0) à C(6)C(6).
  • c) Montrez que C(n)=2Fn+11C(n)=2F_{n+1}-1, où FF est la suite de Fibonacci.
  • d) Calculez C(30)C(30). Combien de temps prendrait le calcul à un million d'appels par seconde ?
  • e) La mémoïsation consiste à retenir les valeurs déjà calculées. Combien d'appels reste-t-il alors ?

Tape tes réponses, la page te dit juste ou faux 0/14

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 15 appels, dont 3 pour fib(2)\mathrm{fib}(2) et 5 pour fib(1)\mathrm{fib}(1)
  • b) C(n)=1+C(n1)+C(n2)C(n) = 1 + C(n-1) + C(n-2) ; 1,1,3,5,9,15,251, 1, 3, 5, 9, 15, 25
  • c) C(n)=2Fn+11C(n) = 2F_{n+1}-1, par récurrence à deux crans
  • d) C(30)=2692537C(30) = 2\,692\,537 appels, soit environ 2,72{,}7 secondes
  • e) Environ 2n+12n+1, soit 61 appels pour n=30n=30 : un gain d'un facteur 44 000

a) L'arbre compte 15 nœuds, donc 15 appels au total. La valeur fib(2)\mathrm{fib}(2) y apparaît TROIS fois, et fib(1)\mathrm{fib}(1) cinq fois. Ces recalculs ne sont pas un défaut de programmation : ils sont inscrits dans la structure de l'arbre, où deux branches issues d'un même nœud redescendent vers des sous-problèmes qui se recouvrent.

b) Un appel à fib(n)\mathrm{fib}(n) compte pour lui-même, plus tous les appels de ses deux sous-arbres : C(n)=1+C(n1)+C(n2)C(n)=1+C(n-1)+C(n-2) pour n2n\geq 2, avec C(0)=C(1)=1C(0)=C(1)=1. Calculs : C(2)=1+1+1=3C(2)=1+1+1=3 ; C(3)=1+3+1=5C(3)=1+3+1=5 ; C(4)=1+5+3=9C(4)=1+5+3=9 ; C(5)=1+9+5=15C(5)=1+9+5=15, ce qui confirme la figure ; C(6)=1+15+9=25C(6)=1+15+9=25.

c) Par récurrence à deux crans. INITIALISATIONS : 2F11=2×11=1=C(0)2F_{1}-1=2\times 1-1=1=C(0) ✓ et 2F21=2×11=1=C(1)2F_{2}-1=2\times 1-1=1=C(1) ✓. HÉRÉDITÉ : en supposant la formule aux rangs kk et k+1k+1, on a C(k+2)=1+C(k+1)+C(k)=1+(2Fk+21)+(2Fk+11)=2(Fk+2+Fk+1)1=2Fk+31C(k+2)=1+C(k+1)+C(k)=1+\left(2F_{k+2}-1\right)+\left(2F_{k+1}-1\right)=2\left(F_{k+2}+F_{k+1}\right)-1=2F_{k+3}-1 ✓. Contrôle : C(5)=2F61=2×81=15C(5)=2F_{6}-1=2\times 8-1=15 ✓.

d) C(30)=2F311=2×13462691=2692537C(30)=2F_{31}-1=2\times 1\,346\,269-1=2\,692\,537 appels, soit environ 2,7 millions. À un million d'appels par seconde, cela prend environ 2,72{,}7 secondes pour un calcul dont le résultat, F30=832040F_{30}=832\,040, tient sur six chiffres. En n=50n=50 il faudrait plus de neuf heures, et en n=80n=80 plus de quarante mille ans : la croissance est en φn\varphi^{n}, où φ1,618\varphi\approx 1{,}618 est le nombre d'or.

e) Avec mémoïsation, chaque valeur de fib(k)\mathrm{fib}(k) n'est calculée qu'UNE fois, pour kk allant de 0 à nn : le nombre d'appels effectifs devient linéaire, de l'ordre de 2n+12n+1. Pour n=30n=30, cela fait 61 appels au lieu de 2,7 millions, un gain d'un facteur 44 000. La leçon dépasse Fibonacci : dès que les sous-problèmes se RECOUVRENT, mémoriser les résultats transforme un coût exponentiel en coût linéaire. C'est le principe de la programmation dynamique.

Partie B : problèmes et raisonnement (/50)

Exercice 6 : Résoudre une relation de récurrence linéaire

Une suite définie par récurrence se calcule pas à pas ; on aimerait une formule directe. Pour les récurrences linéaires à coefficients constants, cette formule existe et s'obtient par une équation du second degré.

  • a) On cherche les suites vérifiant un+2=un+1+unu_{n+2}=u_{n+1}+u_{n} sous la forme un=rnu_{n}=r^{n}. Écrivez l'équation que doit satisfaire rr et résolvez-la.
  • b) Déduisez-en la forme générale des solutions, puis la formule de Binet pour la suite de Fibonacci.
  • c) Vérifiez cette formule pour n=10n=10.
  • d) Montrez que FnF_{n} est l'entier le plus proche de φn5\frac{\varphi^{n}}{\sqrt 5}, où φ\varphi est le nombre d'or, et expliquez pourquoi.
  • e) Appliquez la même méthode à un+2=3un+12unu_{n+2}=3u_{n+1}-2u_{n} avec u0=1u_{0}=1 et u1=3u_{1}=3.

Tape tes réponses, la page te dit juste ou faux 0/14

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) r2r1=0r^{2}-r-1 = 0, de racines φ=1+52\varphi = \frac{1+\sqrt5}{2} et ψ=152\psi = \frac{1-\sqrt5}{2}
  • b) un=Aφn+Bψnu_{n} = A\varphi^{n}+B\psi^{n}, et Fn=φnψn5F_{n} = \dfrac{\varphi^{n}-\psi^{n}}{\sqrt5}
  • c) 122,99190,00812,2360680=55=F10\dfrac{122{,}9919-0{,}0081}{2{,}2360680} = 55 = F_{10}
  • d) ψn/5<12\lvert \psi^{n}/\sqrt5 \rvert < \frac{1}{2} dès n=1n=1 : FnF_{n} est l'entier le plus proche de φn/5\varphi^{n}/\sqrt5
  • e) r23r+2=0r^{2}-3r+2 = 0, racines 1 et 2, A=1A=-1 et B=2B=2 : un=2n+11u_{n} = 2^{n+1}-1

a) En reportant un=rnu_{n}=r^{n} dans la relation : rn+2=rn+1+rnr^{n+2}=r^{n+1}+r^{n}, et en divisant par rnr^{n} non nul, r2=r+1r^{2}=r+1, soit r2r1=0r^{2}-r-1=0. Le discriminant vaut 1+4=51+4=5, donc les deux racines sont φ=1+521,618034\varphi=\frac{1+\sqrt 5}{2}\approx 1{,}618034 et ψ=1520,618034\psi=\frac{1-\sqrt 5}{2}\approx-0{,}618034. Cette équation s'appelle l'équation caractéristique de la récurrence.

b) Comme la relation est LINÉAIRE, toute combinaison un=Aφn+Bψnu_{n}=A\varphi^{n}+B\psi^{n} la vérifie encore, et l'on montre qu'il n'y en a pas d'autres. Les deux conditions initiales fixent AA et BB : F0=0F_{0}=0 donne A+B=0A+B=0, donc B=AB=-A ; F1=1F_{1}=1 donne AφAψ=A5=1A\varphi-A\psi=A\sqrt 5=1, donc A=15A=\frac{1}{\sqrt 5}. D'où la FORMULE DE BINET : Fn=φnψn5F_{n}=\frac{\varphi^{n}-\psi^{n}}{\sqrt 5}. Fait remarquable, cette expression pleine de racines carrées donne un entier pour chaque nn.

c) φ10122,9919\varphi^{10}\approx 122{,}9919 et ψ100,0081\psi^{10}\approx 0{,}0081, donc 122,99190,00812,2360680122,98372,236068055,0000\frac{122{,}9919-0{,}0081}{2{,}2360680}\approx\frac{122{,}9837}{2{,}2360680}\approx 55{,}0000 ✓, ce qui est bien F10=55F_{10}=55. Le calcul direct par la relation donne la même valeur, comme il se doit.

d) Le second terme ψn5\frac{\psi^{n}}{\sqrt 5} a pour module 0,618034n2,236\frac{0{,}618034^{n}}{2{,}236}, qui est inférieur à 12\frac{1}{2} dès n=1n=1 et tend très vite vers 0. L'écart entre FnF_{n} et φn5\frac{\varphi^{n}}{\sqrt 5} est donc toujours inférieur à un demi, ce qui signifie exactement que FnF_{n} est l'entier le plus proche. Vérification en n=10n=10 : 122,99192,236068055,0036\frac{122{,}9919}{2{,}2360680}\approx 55{,}0036, dont l'entier le plus proche est bien 55. C'est cette domination d'une racine sur l'autre qui explique aussi la croissance en φn\varphi^{n} observée à l'exercice 5.

e) Équation caractéristique : r2=3r2r^{2}=3r-2, soit r23r+2=0r^{2}-3r+2=0, de racines r=1r=1 et r=2r=2. Forme générale : un=A×1n+B×2n=A+B2nu_{n}=A\times 1^{n}+B\times 2^{n}=A+B2^{n}. Conditions initiales : u0=A+B=1u_{0}=A+B=1 et u1=A+2B=3u_{1}=A+2B=3. En soustrayant, B=2B=2, puis A=1A=-1. Donc un=2n+11u_{n}=2^{n+1}-1. Contrôle : u0=1u_{0}=1, u1=3u_{1}=3, u2=7u_{2}=7, u3=15u_{3}=15 ✓, ce qui confirme la conjecture formulée à l'exercice 3. Le procédé est identique, seules les racines changent.

Exercice 7 : Diviser pour régner : la recherche dichotomique

Certains algorithmes récursifs ne recouvrent pas leurs sous-problèmes : ils coupent le problème en deux et n'en gardent qu'une moitié. Le coût s'effondre alors au lieu d'exploser.

16 cases, on regarde la 8e8 cases restantes4 cases2 cases, puis 14 etapes pour 16 cases : le cout est le logarithme en base 2
def dichotomie(tableau, cible, debut, fin):
    if debut > fin:
        return -1
    milieu = (debut + fin) // 2
    if tableau[milieu] == cible:
        return milieu
    if tableau[milieu] > cible:
        return dichotomie(tableau, cible, debut, milieu - 1)
    return dichotomie(tableau, cible, milieu + 1, fin)
  • a) Décrivez la recherche dichotomique dans un tableau TRIÉ et écrivez sa version récursive en Python.
  • b) Écrivez la relation de récurrence vérifiée par le nombre T(n)T(n) de comparaisons dans le pire cas, puis résolvez-la.
  • c) Combien de comparaisons faut-il au maximum pour un tableau d'un million d'éléments ? Comparez avec la recherche séquentielle.
  • d) Pourquoi la recherche dichotomique n'a-t-elle pas le problème de recalcul de l'exercice 5 ?
  • e) Le tri fusion vérifie T(n)=2T(n2)+nT(n)=2T\left(\frac{n}{2}\right)+n. Devinez son coût et justifiez par un argument d'arbre.

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) On compare au milieu et l'on ne garde qu'une moitié ; le tableau doit être TRIÉ
  • b) T(n)=T(n/2)+1T(n) = T(n/2)+1 avec T(1)=1T(1)=1, donc T(n)log2n+1T(n) \approx \log_{2}n + 1
  • c) 20 comparaisons contre un million : un rapport de 50 000
  • d) Parce qu'on CHOISIT un sous-problème et qu'on abandonne l'autre : l'arbre est un simple chemin
  • e) nlog2nn\log_{2}n : log2n\log_{2}n niveaux, et nn éléments traités à chaque niveau

a) On compare la valeur cherchée à l'élément du MILIEU. Si elle est égale, c'est fini. Si elle est plus petite, on recommence dans la moitié gauche ; sinon dans la moitié droite. À chaque tour, l'intervalle de recherche est divisé par deux, comme le montre la figure : 16 cases, puis 8, puis 4, puis 2, puis 1. La condition indispensable est que le tableau soit TRIÉ, sans quoi la comparaison au milieu n'apprend rien sur les deux moitiés.

b) T(n)=T(n2)+1T(n)=T\left(\frac{n}{2}\right)+1, avec T(1)=1T(1)=1 : une comparaison au milieu, puis le même problème sur une moitié. En déroulant : T(n)=1+1++1T(n)=1+1+\dots+1 autant de fois qu'on peut diviser nn par 2 avant d'atteindre 1, c'est-à-dire log2n\log_{2}n fois. Donc T(n)log2n+1T(n)\approx\log_{2}n+1, un coût LOGARITHMIQUE.

c) log2(106)=6ln10ln219,93\log_{2}\left(10^{6}\right)=\frac{6\ln 10}{\ln 2}\approx 19{,}93, donc 20 comparaisons suffisent. La recherche séquentielle en demanderait jusqu'à un million, soit 50 000 fois plus. Autrement dit, doubler la taille du tableau ajoute UNE comparaison à la dichotomie et un million à la recherche séquentielle : c'est la différence de nature entre logarithmique et linéaire.

d) Parce que ses deux sous-problèmes ne sont jamais explorés tous les deux : on en CHOISIT un et l'on abandonne l'autre. L'arbre d'appels de Fibonacci se ramifie en deux à chaque nœud, d'où sa taille exponentielle ; celui de la dichotomie est un simple CHEMIN de longueur log2n\log_{2}n. Ce n'est donc pas la récursivité qui coûte cher, c'est la ramification avec recouvrement.

e) Le coût est nlog2nn\log_{2}n. Argument d'arbre : l'arbre des appels a log2n\log_{2}n niveaux, puisque la taille est divisée par deux à chaque descente. À CHAQUE niveau, la fusion traite au total nn éléments, répartis entre les appels de ce niveau : nn en haut, deux fois n2\frac{n}{2} au niveau suivant, quatre fois n4\frac{n}{4} ensuite, toujours nn au total. Le coût global est donc le nombre de niveaux multiplié par le travail par niveau, soit nlog2nn\log_{2}n. Pour un million d'éléments, cela fait environ 20 millions d'opérations, contre 101210^{12} pour un tri par sélection : c'est la raison pour laquelle tous les tris utilisés en pratique sont récursifs.

Exercice 8 : Cinq affirmations à corriger

Chacune des cinq affirmations suivantes est FAUSSE. Dites pourquoi et donnez l'énoncé correct.

  • 1) « Si l'hérédité est démontrée, la propriété est vraie pour tout nn. »
  • 2) « J'ai vérifié la propriété pour nn allant de 1 à 100, elle est donc démontrée. »
  • 3) « Dans l'hérédité, on suppose que P(k)P(k) est vraie, ce qui revient à supposer ce qu'on veut démontrer. »
  • 4) « Une fonction récursive est toujours plus lente que sa version itérative. »
  • 5) « Pour démontrer une propriété de la suite de Fibonacci, une seule initialisation suffit. »

Tape tes réponses, la page te dit juste ou faux 0/10

1)
2)
3)
4)
5)
Voir la correction

Réponses

  • 1) FAUX : il manque l'INITIALISATION, sans quoi la chaîne d'implications ne démarre jamais
  • 2) FAUX : n2+n+41n^{2}+n+41 est premier jusqu'à n=39n=39 et cesse de l'être en n=40n=40
  • 3) FAUX : on démontre l'IMPLICATION, pas P(k)P(k). Une implication peut être vraie à hypothèse fausse
  • 4) FAUX : la dichotomie et le tri fusion récursifs sont optimaux. C'est la ramification avec recouvrement qui coûte
  • 5) FAUX : DEUX initialisations, la relation dépendant de deux rangs antérieurs

1) FAUX. Il manque l'INITIALISATION, et sans elle l'hérédité ne prouve rien. La propriété « n=n+1n=n+1 » a une hérédité parfaitement valide, puisque k=k+1k=k+1 entraîne k+1=k+2k+1=k+2, et elle est pourtant fausse à tous les rangs. L'hérédité établit une chaîne d'implications ; encore faut-il que quelque chose la déclenche.

2) FAUX. Cent vérifications ne valent pas une démonstration, et l'exemple d'Euler le montre : n2+n+41n^{2}+n+41 est premier pour nn de 0 à 39 et cesse de l'être en n=40n=40, où l'on obtient 1681=4121681=41^{2}. Il existe des propriétés dont le premier contre-exemple dépasse le milliard. Le rôle de l'hérédité est justement de remplacer une infinité de vérifications, ce qu'aucun nombre fini d'essais ne peut faire.

3) FAUX, et c'est le malentendu le plus tenace du chapitre. On ne suppose PAS que P(k)P(k) est vraie : on démontre l'IMPLICATION « si P(k)P(k) alors P(k+1)P(k+1) ». Une implication peut être vraie alors que son hypothèse est fausse, et l'on n'a donc rien affirmé sur P(k)P(k). Ce que l'on démontre, c'est un mécanisme de transmission ; l'initialisation, elle, fournit un point de départ vrai, et la conjonction des deux donne la conclusion.

4) FAUX. La récursivité n'est pas coûteuse en soi : c'est la RAMIFICATION AVEC RECOUVREMENT qui l'est. La recherche dichotomique récursive coûte log2n\log_{2}n comparaisons, exactement comme sa version itérative ; le tri fusion récursif en nlog2nn\log_{2}n bat tout tri quadratique itératif. Ce qui rend fib\mathrm{fib} naïve désastreuse, c'est qu'elle recalcule les mêmes valeurs, et la mémoïsation la ramène à un coût linéaire sans lui retirer sa récursivité.

5) FAUX. La relation Fn+2=Fn+1+FnF_{n+2}=F_{n+1}+F_{n} fait intervenir DEUX rangs antérieurs, il faut donc DEUX initialisations, aux rangs 0 et 1. Avec une seule, on ne peut même pas calculer F2F_{2}. Règle générale : le nombre d'initialisations doit être égal au nombre de rangs antérieurs dont dépend la relation.

Exercice 9 : Problème : les tours de Hanoï

Le problème des tours de Hanoï est l'exemple parfait d'une définition récursive qui donne à la fois l'algorithme et son coût. Il montre aussi qu'un coût exponentiel peut être inévitable et non le signe d'un mauvais programme.

ABCdeplacer 4 disques demande 15 coups, soit 2 puissance 4 moins 1un disque ne se pose jamais sur un plus petit
def hanoi(n, depart, arrivee, intermediaire):
    if n == 0:
        return 0
    coups = hanoi(n - 1, depart, intermediaire, arrivee)
    coups = coups + 1
    return coups + hanoi(n - 1, intermediaire, arrivee, depart)
  • a) Rappelez les règles du jeu, puis décrivez la stratégie récursive pour déplacer nn disques du piquet A au piquet C.
  • b) Notez H(n)H(n) le nombre de déplacements. Écrivez la relation de récurrence et calculez H(1)H(1) à H(5)H(5).
  • c) Démontrez par récurrence que H(n)=2n1H(n)=2^{n}-1.
  • d) La légende parle de 64 disques déplacés par des moines à raison d'un par seconde. Combien de temps cela prendrait-il ?
  • e) Peut-on faire mieux que 2n12^{n}-1 ? Que dit ce problème sur le rapport entre récursivité et efficacité ?

Tape tes réponses, la page te dit juste ou faux 0/13

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Un disque à la fois, jamais sur un plus petit ; on déplace n1n-1 disques vers B, le grand vers C, puis n1n-1 de B vers C
  • b) H(n)=2H(n1)+1H(n) = 2H(n-1)+1 avec H(1)=1H(1)=1 : 1,3,7,15,311, 3, 7, 15, 31
  • c) H(k+1)=2(2k1)+1=2k+11H(k+1) = 2(2^{k}-1)+1 = 2^{k+1}-1
  • d) 2641=184467440737095516152^{64}-1 = 18\,446\,744\,073\,709\,551\,615 déplacements, soit environ 585 milliards d'années
  • e) NON : 2n12^{n}-1 est le minimum. Le coût exponentiel est dans le problème, non dans l'algorithme

a) RÈGLES : on déplace un seul disque à la fois, en le prenant au sommet d'un piquet, et l'on ne pose jamais un disque sur un plus petit. STRATÉGIE : pour déplacer nn disques de A vers C, on déplace d'abord les n1n-1 disques du dessus de A vers B, en utilisant C comme intermédiaire ; on déplace ensuite le grand disque de A vers C, seul mouvement direct ; on déplace enfin les n1n-1 disques de B vers C en utilisant A. Le cas de base est n=1n=1, où l'on déplace le disque unique.

b) La stratégie donne directement H(n)=2H(n1)+1H(n)=2H(n-1)+1 avec H(1)=1H(1)=1. Calculs : H(2)=3H(2)=3 ; H(3)=7H(3)=7 ; H(4)=15H(4)=15, ce qu'annonce la figure ; H(5)=31H(5)=31. On reconnaît la suite 2n12^{n}-1, ce que la question suivante démontre.

c) INITIALISATION : H(1)=1=211H(1)=1=2^{1}-1 ✓. HÉRÉDITÉ : supposons H(k)=2k1H(k)=2^{k}-1. Alors H(k+1)=2H(k)+1=2(2k1)+1=2k+12+1=2k+11H(k+1)=2H(k)+1=2\left(2^{k}-1\right)+1=2^{k+1}-2+1=2^{k+1}-1 ✓. La formule est donc établie pour tout n1n\geq 1. Remarquez que la démonstration reprend exactement la structure de l'algorithme : c'est le propre des problèmes récursifs, la preuve et le programme se ressemblent.

d) H(64)=2641=18446744073709551615H(64)=2^{64}-1=18\,446\,744\,073\,709\,551\,615 déplacements. À raison d'un par seconde, cela fait environ 5,85×10115{,}85\times 10^{11} années, soit près de 585 milliards d'années. À titre de comparaison, l'âge de l'Univers est estimé à environ 13,8 milliards d'années : les moines de la légende auraient donc besoin d'une durée quarante fois supérieure. C'est le meilleur argument pédagogique qui soit contre l'idée qu'un ordinateur plus rapide résout tout.

e) NON, on ne peut pas faire mieux : on démontre que 2n12^{n}-1 est le nombre MINIMAL de déplacements, car le grand disque doit être déplacé au moins une fois, et pour cela les n1n-1 autres doivent tous être empilés ailleurs. Le coût exponentiel n'est donc pas la faute de l'algorithme récursif, il est dans le PROBLÈME lui-même. C'est la distinction fondamentale avec Fibonacci naïf, dont le coût exponentiel venait de la méthode et disparaissait par mémoïsation. Avant d'optimiser un programme lent, il faut donc se demander lequel des deux cas on a sous les yeux.

Exercice 10 : Problème : reconnaître le bon outil devant un algorithme récursif

Trois algorithmes récursifs, trois coûts radicalement différents. Savoir lequel on a sous les yeux avant d'écrire une ligne de code est la compétence que ce chapitre doit laisser.

2468101214161820510152025303540nombre d'appels2n : version avec memoirenombre d'or puissance n : fib naiflog2(n) : dichotomie
  • a) Classez par coût croissant : la recherche dichotomique, le calcul de Fibonacci mémoïsé, les tours de Hanoï, le tri fusion, le calcul de Fibonacci naïf.
  • b) Pour chacun, écrivez la relation de récurrence sur le coût.
  • c) Sur la figure, associez chaque courbe à l'un de ces algorithmes et dites laquelle devient inutilisable en premier.
  • d) Un algorithme récursif est lent. Donnez la question à se poser en premier, et les deux réponses possibles avec leur remède.
  • e) Un étudiant remplace une fonction récursive par une boucle et constate que le programme reste aussi lent. Qu'en conclure ?

Tape tes réponses, la page te dit juste ou faux 0/13

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Dichotomie log2n\log_{2}n, Fibonacci mémoïsé nn, tri fusion nlog2nn\log_{2}n, Fibonacci naïf φn\varphi^{n}, Hanoï 2n2^{n}
  • b) T(n/2)+1T(n/2)+1 ; T(n1)+1T(n-1)+1 ; 2T(n/2)+n2T(n/2)+n ; T(n1)+T(n2)+1T(n-1)+T(n-2)+1 ; 2T(n1)+12T(n-1)+1
  • c) La plus plate est la dichotomie, la droite est le mémoïsé, la verticale est Fibonacci naïf, inutilisable avant n=8n=8
  • d) Les sous-problèmes se recouvrent-ils ? Oui : mémoïsation. Non : le coût est intrinsèque au problème
  • e) Que le problème ne venait pas de la récursivité : à schéma égal, boucle et récursion font le même nombre d'opérations

a) Du moins coûteux au plus coûteux : DICHOTOMIE en log2n\log_{2}n ; FIBONACCI MÉMOÏSÉ en nn ; TRI FUSION en nlog2nn\log_{2}n ; puis, très loin derrière, FIBONACCI NAÏF en φn\varphi^{n} et HANOÏ en 2n2^{n}, tous deux exponentiels, Hanoï étant le plus coûteux puisque 2>φ2>\varphi.

b) DICHOTOMIE : T(n)=T(n2)+1T(n)=T\left(\frac{n}{2}\right)+1. FIBONACCI MÉMOÏSÉ : T(n)=T(n1)+1T(n)=T(n-1)+1, chaque valeur n'étant calculée qu'une fois. TRI FUSION : T(n)=2T(n2)+nT(n)=2T\left(\frac{n}{2}\right)+n. FIBONACCI NAÏF : T(n)=T(n1)+T(n2)+1T(n)=T(n-1)+T(n-2)+1. HANOÏ : T(n)=2T(n1)+1T(n)=2T(n-1)+1. Deux paramètres décident de tout, le nombre d'appels récursifs et la façon dont la taille décroît : divisée par deux, le coût reste modeste ; diminuée de 1 avec deux appels, il explose.

c) La courbe la plus plate est le logarithme, donc la DICHOTOMIE. La droite est le coût linéaire, donc FIBONACCI MÉMOÏSÉ. La courbe qui monte à la verticale est la puissance du nombre d'or, donc FIBONACCI NAÏF. C'est elle qui devient inutilisable en premier : sur la figure, elle dépasse déjà les deux autres avant n=8n=8, alors que la différence entre logarithme et linéaire reste négligeable jusqu'à des tailles énormes. Une croissance exponentielle rattrape n'importe quelle croissance polynomiale, et elle le fait toujours plus tôt qu'on ne le croit.

d) La question à se poser est : les sous-problèmes se RECOUVRENT-ils ? PREMIÈRE RÉPONSE, oui, comme dans Fibonacci naïf où fib(2)\mathrm{fib}(2) est recalculé trois fois pour n=5n=5 : le remède est la MÉMOÏSATION, qui ramène le coût à linéaire sans changer la structure du programme. SECONDE RÉPONSE, non, comme dans les tours de Hanoï où chaque appel traite une configuration nouvelle : le coût est alors intrinsèque au problème, aucune mémoire ne l'améliorera, et il faut soit changer de problème, soit accepter une solution approchée.

e) Que le problème ne venait pas de la récursivité. Une boucle et une récursion qui suivent le MÊME schéma de calcul font le même nombre d'opérations : la transformation ne change que la gestion de la pile, ce qui économise de la mémoire et un peu de temps constant, jamais un ordre de grandeur. Si le programme reste lent, c'est que le coût est dans l'algorithme lui-même, et il faut revenir à la question d plutôt que continuer à réécrire la même méthode sous une autre forme.

Chapitre précédent Théorie des ensembles et relations Chapitre suivant Introduction aux graphes et aux matrices

Voir aussi

Vous cherchez un tuteur en mathématiques pour l'informatique 201-N11 à Montréal ?

Contactez-moi pour une première séance. On travaille la récurrence et la récursivité au niveau réel des évaluations, de la rédaction d'une hérédité jusqu'à l'analyse du coût d'un algorithme.

Site par Studio Squalli