Mathématiques Discrètes et Algorithmique - LMPT

publicité
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 ! ! !
Téléchargement