Probleme – Élever à une puissance, et Fibonacci en
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 15 — Diviser pour régner
Énoncé
- Écrire l'exponentiation rapide, avec sa preuve, et compter ses multiplications.
- Montrer que .
- En déduire un calcul de en opérations, et le comparer aux deux autres méthodes.
Corrigé
1. L'exponentiation rapide.
(* Renvoie x^n. Précondition : n >= 0.
Complexité : Theta(log n) multiplications. *)
let rec puissance x n =
if n = 0 then 1
else if n mod 2 = 0 then let y = puissance x (n / 2) in y * y
else x * puissance x (n - 1)
Terminaison : variant , qui décroît strictement — soit divisé par deux, soit diminué de ; et deux tours ne peuvent pas être impairs de suite, donc est au moins divisé par deux tous les deux appels : appels.
Correction : par récurrence forte sur . Si est pair, ; s'il est impair, . Le point à ne pas manquer est le let y = ... in y <em> y : écrire puissance x (n/2) </em> puissance x (n/2) ferait deux appels au lieu d'un, et l'on retomberait sur — tout le gain perdu par une lettre.
Multiplications comptées :
| méthode naïve | exponentiation rapide | |
|---|---|---|
| 10 | 10 | 5 |
| 100 | 9 | |
| 15 | ||
| 22 |
(Le calcul a été fait modulo pour que rien ne déborde ; le comptage ne s'en trouve pas modifié.)
2. La formule matricielle. Posons . Par récurrence sur .
Base : avec , .
Hérédité : si , alors
La récurrence de Fibonacci est exactement ce qui fait fonctionner le produit.
3. Le calcul et la comparaison. On applique l'exponentiation rapide au produit de matrices , qui est associatif : est le coefficient en haut à droite de .
type mat = { a : int; b : int; c : int; d : int }
let produit m n =
{ a = m.a*n.a + m.b*n.c; b = m.a*n.b + m.b*n.d;
c = m.c*n.a + m.d*n.c; d = m.c*n.b + m.d*n.d }
(* F(n) en Theta(log n) produits de matrices 2x2. Précondition : n >= 0. *)
let rec puiss_mat m n =
if n = 0 then { a = 1; b = 0; c = 0; d = 1 }
else if n mod 2 = 0 then let p = puiss_mat m (n / 2) in produit p p
else produit m (puiss_mat m (n - 1))
let fibonacci n =
if n = 0 then 0 else (puiss_mat { a = 1; b = 1; c = 1; d = 0 } n).b
| récursif naïf | itératif | matriciel | ||
|---|---|---|---|---|
| 10 | 177 appels | 10 additions | 5 produits | |
| 20 | appels | 20 | 6 produits | |
| 30 | appels | 30 | 8 produits | |
| 90 | hors de portée | 90 | 10 produits |
Les trois méthodes ont été confrontées et donnent les mêmes valeurs partout où les trois sont calculables.
Ce que ce tableau met en scène — et il y a trois régimes, pas deux.
Le récursif naïf est exponentiel : appels, parce qu'il recalcule les mêmes valeurs. C'est le défaut du chapitre chap:dynamique, et la mémoïsation le ramène à .
L'itératif est en additions, et c'est déjà excellent. Diviser pour régner n'a rien à voir avec la correction de la récursion naïve : les deux améliorations sont indépendantes.
Le matriciel est en produits. Il n'y a plus rien à mémoïser — il n'y a plus de sous-problème répété. Le gain vient d'ailleurs : d'avoir remarqué que la récurrence est linéaire, donc représentable par une matrice, donc justiciable de l'exponentiation rapide. Le même procédé accélère toute récurrence linéaire à coefficients constants.
Une nuance sur le « ». tient encore dans un entier de bits ; non. En arithmétique exacte, les nombres manipulés ont chiffres, et chaque produit coûte alors par Karatsuba. La complexité en opérations est logarithmique ; la complexité en temps ne l'est pas. Compter les opérations suppose qu'elles sont à coût constant, et il faut le dire.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.