informatique (MP2I/MPI) · Chapitre 5 · Exercice de TD
Le let qui change la complexité
Récursivité · niveau 2
Énoncé
Ces deux exponentiations rendent la même valeur. Combien de multiplications fait chacune pour ?
(* A *) let rec puiss x n =
if n = 0 then 1
else let m = puiss x (n/2) in
if n mod 2 = 0 then m * m else m * m * x
(* B *) let rec puiss x n =
if n = 0 then 1
else if n mod 2 = 0 then puiss x (n/2) * puiss x (n/2)
else puiss x (n/2) * puiss x (n/2) * xCorrigé
Mesuré, en instrumentant les deux fonctions d'un compteur :
n=10 A : 6 multiplications, 5 appels B : 25 multiplications, 31 appels
n=20 A : 7 multiplications, 6 appels B : 51 multiplications, 63 appels
n=40 A : 8 multiplications, 7 appels B : 103 multiplications, 127 appels
Pourquoi. Dans A, le let m = ... calcule une fois et le nomme. La récurrence est , deuxième forme du cours avec : .
Dans B, l'expression puiss x (n/2) apparaît deux fois, donc elle est évaluée deux fois. La récurrence devient , soit : le nombre d'appels double à chaque niveau, il y en a , et . On mesure bien appels pour , contre pour A.
Le piège nommé. Une variable n'est pas un raccourci d'écriture : en écrivant deux fois la même expression, on demande deux fois le même calcul. Tout le gain de l'exponentiation rapide — le gain qui fait passer de à — tient à ce let. Et la version B est même pire que la méthode naïve à multiplications, puisqu'elle en fait pour .
À retenir comme réflexe : dès qu'un appel récursif figure deux fois dans une expression, comptez les nœuds de l'arbre des appels avant de conclure quoi que ce soit sur le coût.