Université François Rabelais de Tours Laboratoire de Mathématiques et Physique Théorique Mathématiques Discrètes et Algorithmique UE 5-4 Option Semestre 5 3. Complexité La théorie de la complexité vise à savoir si la réponse à un problème peut être donnée efficacement ou, au contraire être inatteignable en pratique. Pour cela, elle se fonde sur une estimation théorique des temps de calcul et de la mémoire utilisée. Nous ne considérerons ici que l’estimation du temps de calcul. Il y en a deux types : • Etude du cas le plus défavorable : On estime le nombre d’opérations nécessaires à la complétion de l’algorithme dans le pire des cas (on supposera toujours que le nombre de boucles est maximum) • La complexité en moyenne : On estime le nombre moyen d’opérations nécessaires à la complétion de l’algorithme en fonction de toutes les entrées possibles. La complexité en moyenne est beaucoup plus difficile à calculer que l’autre. Nous nous concentrerons sur la complexité dans le cas le plus défavorable. Exemple 3.1. Considérons l’algorithme suivant qui détermine si l’élément x appartient à la liste L et si oui, renvoie sa position. Données : Liste (a1 , a2 , . . . , an ), x position := 0 i := 1 pour i = 1 à n faire si x = ai alors retourner position := i fin fin Résultat : position Algorithme 8: Recherche d’un élément Lorsque l’on écrit "retourner", cela signifie que l’algorithme s’arrête (on sort de la boucle pour) et renvoie la valeur. Complexité dans le pire des cas. A chaque étape de la boucle pour, on effectue 1 comparaisons. Dans le pire des cas, c’est à dire si on parcourt la boucle n-fois, on aura fait n comparaisons. Complexité en moyenne Si x est le premier terme, 1 comparaisons sont nécessaires. Si x est en seconde position, 2 comparaisons sont nécessaires. En général, si x est en iième-position, on a besoin de i comparaisons. Le nombre moyen de comparaison est donc n+1 1 + 2 + 3 + ... + n = n 2 3.1. Comparaison de fonctions. Définition 3.2. Soient f, g : R −→ R (ou N −→ R). On dit que f est O(g) si et seulement si il existe des constantes C, k ∈ R telles que |f (x)| ≤ C|g(x)| pour tout x > k On écrira quelque-fois f = O(g) ou encore f (x) = O(g(x)). On dit que C et k sont les témoins de la relation f = O(g). Intuitivement, cette relation dit que f grandit plus lentement qu’un multiple de g. Exemple 3.3. Montrons que la fonction f (x) = x2 + 2x + 1 est O(x2 ). Lorsque x > 1, on a 0 ≤ x2 + 2x + 1 ≤ x2 + 2x2 + x2 = 4x2 . Par conséquence, si on choisit C = 4 et k = 1, on voit que f = O(x2 ). 1 2 On peut être plus précis. En effet, pour x > 2, on a 2x < x2 et 1 < x2 . Ainsi 0 ≤ x2 + 2x + 1 ≤ x2 + x2 + x2 = 3x2 et on peut choisir C = 3 et k = 2. En fait, il est possible de montrer que pour tout C > 1, il existe k ∈ R tel que 0 ≤ f (x) ≤ Cx2 pour tout x > k On a la caractérisation suivante. Théorème 3.4. On a f (x) = O(g(x)) si et seulement si lim sup | x→+∞ f (x) |<∞ g(x) (x) En particulier, si lim | fg(x) | < ∞ alors f (x) = O(g(x)). x→+∞ Corollaire 3.5. Soit f (x) = an xn + . . . + a1 x + a0 où ai ∈ R pour tout 1 ≤ i ≤ n. On a f (x) = O(xn ). Pn Exemple 3.6. (1) Montrons que Sn := i=1 i est O(n2 ). On a pour tout n ≥ 1 Sn = 1 + 2 + . . . + n ≤ n + n + . . . + n = n2 . Ainsi, Sn = O(n2 ). On aurait aussi pu montrer que Sn = n(n+1) 2 (2) On cherche un grand-O de factorielle n. On rappelle que n! = n · (n − 1) . . . 2 · 1. On a n! = n · (n − 1) . . . 2 · 1 ≤ n · n . . . n = nn On a donc n! = O(nn ). (3) Montrons que log n = O(n). On utilise le Théorème 3.4. On a lim n→∞ log(n) =0 n et le résultat suit. La figure suivante illustre à quelle vitesse les différentes fonctions croissent. 3 Théorème 3.7. Soient f1 , f2 , g1 , g2 , g, f des fonctions réelles. On a i) Si f1 = O(g1 ) et f2 = O(g2 ) alors f1 + f2 = O(g1 + g2 ) ii) Si f1 = O(g1 ) et f2 = O(g2 ) alors f1 · f2 = O(g1 · g2 ) iii) Si f1 = O(g) et f2 = O(g) alors f1 + f2 = O(g). Définition 3.8. Soient f, g : R −→ R (ou N −→ R). On dit que • f est Ω(g) si et seulement si il existe des constantes C, k ∈ R telles que |f (x)| ≥ C|g(x)| pour tout x > k • f est Θ(g) si et seulement si f = O(g) et f = Ω(g). En d’autres termes, f = Θ(g) si et seulement si il existe des constantes C1 , C2 , k telles que C1 g(x) ≤ |f (x)| ≤ C2 |g(x)| pour tout x > k 3.2. Exemples d’étude de complexité. Exemple 3.9. Considérons l’algorithme suivant pour déterminer le maximum d’une liste d’entiers. Données : Liste d’entiers L : (a1 , a2 , . . . , an ) max := a1 pour i = 2 à n faire si max < ai alors max := ai fin fin Résultat : max Algorithme 9: Recherche du maximum dans une liste Nous allons calculer la complexité de cet algorithme en terme de nombre de comparaisons, puisque c’est l’opération élémentaire utilisée dans cet algorithme. La boucle "pour" est parcourue n − 1 fois, à chaque fois il y a un test max < ai , on a donc fait n − 1 comparaisons. Exemple 3.10. On rappelle l’algorithme du tri à bulle. Données : Liste d’entiers L : (a1 , a2 , . . . , an ) Résultat : Liste triée pour i=1 à n-1 faire pour j=1 à n-i faire si aj > aj alors échanger aj et aj+1 fin fin fin Le nombre de comparaisons pour mener à bien cet algorithme est (n − 1) + (n − 2) + . . . + 1 = | {z } | {z } boucle i = 1 n(n − 1) ce qui est Θ(n2 ). 2 boucle i = 2 Exemple 3.11. On rappelle l’algorithme du tri par insertion. Données : Liste d’entiers L = (a1 , a2 , . . . , an ) Résultat : Liste d’entier triée pour i=2 à n faire j := i ; tant que j > 1 et aj−1 > aj faire Echanger aj et aj−1 ; j := j − 1 fin fin retourner L Algorithme 10: Tri par insertion 4 Encore une fois, déterminons le nombre de comparaison nécessaire pour mener à bien cet algorithme. Cet algorithme insère le jième élément à la bonne position parmi les j − 1 premiers (qui sont, eux, déjà triés). Cette insertion est faite de manière linéaire : on compare successivement aj avec les ai , i ≤ j − 1 jusqu’à qu’un des éléments soient plus grand que aj . Dans le pire des cas, l’insertion coûte j comparaisons. Finalement, le nombre de comparaisons est : n(n + 1) −1 2 + 3 + ... + n = 2 Ce qui est Θ(n2 ). Remarque 3.12. Il est important de noter que cet algorithme peut utiliser beaucoup moins de comparaisons dans des cas plus favorables ! Exemple 3.13. On rappelle que l’algorithme d’Euclide pour déterminer pgcd(a, b). On calcule successivement a = bq1 + r1 b = r1 q2 + r2 r1 = r2 q3 + r3 ... rk = rk+1 qk+2 + rk+2 Le dernier reste non nul est alors le pgcd rechercher. Déterminons le nombre de divisions nécessaires. Tout d’abord on voit facilement que la suite des restes est strictement decroissante. Montrons que pour tout j, on a rj+2 < 21 rj . Si rj+1 < 21 rj alors c’est clair. Si rj+1 ≥ 12 rj alors rj = rj+1 · 1 + rj+2 1 2 rj . et rj+2 = rj − rj+1 < Soit ` tel que 2`−1 ≤ a < 2` . Toutes les deux etapes, le reste est divisée par deux, ainsi il y a au plus 2 · log2 (a) divisions. 3.3. Comprendre la complexité des algorithmes. Un algorithme est dit de complexité polynomiale si il est Θ(nb ) pour un certain b ∈ N∗ . Par exemple, le tri à bulle est de complexité polynomiale, nous avons vu qu’il est d’ordre Θ(n2 ). Dire qu’un algorithme est d’ordre Θ(n2 ) doit se comprendre de la manière suivante. Si, on lui donne en entrée, un élément de taille n, l’algorithme va effectuer un nombre d’opérations de la forme Cn2 pour une constante C. Si on lui donne un argument de taille 2n, il va effectuer C(2n)2 = 4.Cn2 et donc prendre 4 fois plus de temps. De la même manière un algorithme Θ(n3 ) mettra 8 fois plus de temps pour un argument de taille 2n que pour un argument de taille n. Le tableau suivant donne une idée plus précise de ce phénomène. Le temps d’une opération est estimé à 10−11 seconde. En cryptographie, il n’est pas rare d’avoir des arguments de taille de l’ordre de 10150 . On imagine alors aisément le besoin d’avoir des algorithmes efficaces ! ! !