Vous êtes sur la page 1sur 14

École de technologie supérieure LOG320 – Structures de données et algorithmes

Cours #1
Analyse asymptotique

Introduction
L’analyse asymptotique permet de déterminer le comportement d’un algorithme lorsque
la taille des données d’entrées augmente. Cette information permet à l’ingénieur de
déterminer la meilleure solution pour résoudre le plus efficacement possible un problème
donné.

Ce document est une introduction à l’analyse d’algorithme et permet d’introduire les


différentes notations utilisées dans le monde des algorithmes.

Analyse précise des algorithmes


Il est important de pouvoir déterminer le comportement d’un algorithme en fonction de
la taille des données d’entrée de la manière la plus indépendante possible de toute
influence extérieure telle que l’architecture du processeur, la quantité de mémoire
disponible ou encore le langage de programmation utilisé. Il est possible de déterminer
le comportement via l’analyse du pseudocode de l’algorithme. Comme point de départ,
considérez l’algorithme suivant :

Algorithme(T[1..n])
1. d=∞
2. pour i = 1 à n
3. pour j = 1 à n
4. si i ≠ j et |T[i] – T[j]| < d
5. d = |T[i] – T[j]|
6. retourner d

Il est possible de déterminer le nombre de fois que chacune des lignes sera exécutée en
fonction de la taille des données d’entrée, qui dans cet exemple, est la taille du tableau T
qui contient n éléments.

La première ligne, sera exécutée une seule fois, peu importe la valeur de n. Par contre,
le nombre d’itérations de la boucle à la ligne 2, dépend de la valeur de n. Elle sera donc
exécutée n+1 fois :

Analyse asymptotique 1
• n fois pour toutes les valeurs de 1,2,3,…,n-1,n
• 1 fois supplémentaire pour déterminer que i est plus grand que n et donc, qu’il
faut terminer la boucle.

La boucle de la ligne 3 dépend aussi de taille du tableau T, elle sera donc exécutée n+1
fois, comme pour la boucle de la ligne 3. Par contre, comme cette boucle est imbriquée
dans la boucle de la ligne 2, les n+1 exécutions seront faites n fois. Donc, au total, la ligne
3 sera exécutée n(n+1) fois.

La ligne 4, quant à elle, sera exécutée n2 fois étant donné qu’elle se trouve dans la boucle
de la ligne 3, est elle-même exécutée n fois étant donné qu’elle est imbriquée dans la
boucle de la ligne 2.

La ligne 5 est un peu différente. En effet, le nombre d’exécutions va dépendre des valeurs
qui se trouvent dans le tableau T. Dans le meilleur cas, elle ne sera jamais exécutée si la
condition de la ligne 4 n’est jamais vraie. Dans le pire cas, elle sera exécutée n2-n fois si
cette même condition est toujours vraie. Notez que la soustraction du n vient du fait que
la condition est fausse si i est égal à j, ce qui se produit n fois.

Finalement, la dernière ligne de l’algorithme est exécutée n fois. Le nombre de fois que
chacune des lignes est exécutée est résumé ici :

Algorithme(T[1..n])
1. d=∞ 1
2. pour i = 1 à n n+1
3. pour j = 1 à n n(n+1)
4. si i ≠ j et |T[i] – T[j]| < d n2
5. d = |T[i] – T[j]| Entre 0 et n2-n
6. retourner d 1

Le temps total d’exécution T(n) de cet algorithme, dans le pire cas, est une fonction qui
correspond à la somme de chacune des lignes :

𝑇(𝑛) = 1 + (𝑛 + 1) + 𝑛(𝑛 + 1) + 𝑛! + 𝑛! − 𝑛 + 1 = 3𝑛! + 𝑛 + 3

Si le temps d’exécution de chacune des lignes était connu, il serait possible d’obtenir le
temps d’exécution total de l’algorithme en fonction de n. Cependant, ces valeurs sont à
peu près impossibles à obtenir. Par conséquent, l’utilisation principale de cette analyse
est d’avoir une idée du comportement de l’algorithme en fonction de n permettant ainsi

Analyse asymptotique 2
de déterminer si l’algorithme répond aux requis ou encore de le comparer à d’autres
algorithmes pour le même problème.

Deuxième version de l’algorithme

Voici une version différente du même algorithme :

Algorithme(T[1..n])
1. d=∞
2. pour i = 1 à n
3. pour j = i+1 à n
4. si |T[i] – T[j]| < d
5. d = |T[i] – T[j]|
6. retourner d

L’analyse de cette deuxième version nous permettra d’effectuer une comparaison entre
les deux algorithmes.

Tout comme l’algorithme original, les lignes 1 et 2 sont respectivement exécutées 1 et


n+1 fois. La ligne 3 est par contre différente puisque la valeur de j commence à i+1 au
lieu de commencer à 1 comme dans l’algorithme original. Une façon de déterminer
combien de fois la ligne 3 est exécutée est de vérifier le nombre d’exécutions en fonction
de la valeur de i pour n=6:

Valeur de i Valeurs possibles de j Nombre d’itérations


1 2,3,4,5,6,7 6
2 3,4,5,6,7 5
3 4,5,6,7 4
4 5,6,7 3
5 6,7 2
6 7 1

Le nombre total d’itérations de la ligne 3 sera donc la somme des itérations pour chacune
des valeurs de i :
6+5+4+3+2+1

Si on généralise cette équation pour n’importe quelle valeur de n, on obtient :

Analyse asymptotique 3
"

𝑛 + 𝑛 − 1 + 𝑛 − 2 + ⋯ + 2 + 1 = 0 𝑖
#$%
𝑛(𝑛 + 1)
=
2
Faisons maintenant le même exercice pour la ligne 4 :

Valeur de i Valeurs possibles de j Nombre d’itérations


1 2,3,4,5,6 5
2 3,4,5,6 4
3 4,5,6 3
4 5,6 2
5 6 1
6 0

L’examen du tableau permet de remarquer qu’il est très similaire au tableau précédant à
la différence près qu’il y a une itération de moins pour chaque valeur de i. Le nombre
d’itérations est donc :

" " "

0(𝑖 − 1) = 0 𝑖 − 0 1
#$% #$% #$%
𝑛(𝑛 + 1)
= −𝑛
2
𝑛(𝑛 − 1)
=
2
"("'%)
La ligne 5 qui dépend du contenu du tableau T, sera exécutée entre 0 et !
fois. En
résumé, nous avons donc :

Algorithme2(T[1..n])
1. d=∞ 1
2. pour i = 1 à n n+1
3. pour j = i+1 à n 𝑛(𝑛 + 1)
2
4. si |T[i] – T[j]| < d 𝑛(𝑛 − 1)
2
5. d = |T[i] – T[j]| "("'%)
Entre 0 et !
-n

6. retourner d 1

Analyse asymptotique 4
Dans le pire cas, le temps d’exécution T(n) de cet algorithme sera de :

𝑛(𝑛 + 1) 𝑛(𝑛 − 1) 𝑛(𝑛 − 1) 3𝑛! + 𝑛 + 6


𝑇(𝑛) = 1 + (𝑛 + 1) + + + +1=
2 2 2 2

Pour comparer les deux algorithmes, les fonctions T(n) de chacun des algorithmes sont
présentées graphiquement sur la figure suivante :

Comparaison des deux


versions de l'algorithme
T(n)

Version 1 Version 2

Figure 1 : Comparaison des deux versions de l’algorithme en fonction de n

Sur ce graphique, il est facile de voir que le deuxième algorithme est beaucoup plus rapide
que le premier.

Le principal inconvénient de cette approche est que les calculs nécessaires pour obtenir
la fonction T(n) peuvent devenir rapidement complexes et difficiles à faire. De plus, de
façon générale, c’est le comportement de l’algorithme en fonction de la taille des
données d’entrée qui est intéressant et non la fonction précise. C’est ce que l’analyse
asymptotique permet d’obtenir.

Analyse asymptotique
L’analyse asymptotique permet de déterminer comment un algorithme se comporte
lorsque la taille des données d’entrée augmente sans avoir à faire les calculs précis. À titre
d’exemple, la figure 2 montre la comparaison de la courbe exacte de l’équation 𝑇(𝑛) =
)"! *"*+
!
et d’une version simplifiée de la même courbe : 𝑇(𝑛) = 𝑛! . Les courbes
montrent le même comportement lorsque la taille augmente, ce qui est l’aspect
important lors de l’analyse d’un algorithme.

Analyse asymptotique 5
Comparaison d'une fonction avec
sa version simplifiée

Fonction originale Fonction simplifiée

"#! $#$%
Figure 2 : Comparaison des fonctions T(n) = et T(n) = n&
&

Afin d’obtenir une équation plus simple, les règles suivantes sont appliquées :
• seul le monôme avec le degré le plus élevé est conservé;
• les constantes sont éliminées.

Il existe 5 notations standards pour décrire le comportement des algorithmes. Chacune


des notations est utilisée pour montrer une propriété de l’algorithme.

Notation Grand-O

Cette notation permet d’expliquer le comportement de l’algorithme dans le pire cas,


c’est-à-dire lorsque les données d’entrée sont les pires possibles, au point de vue du
nombre d’opérations exécutées par l’algorithme. Une fonction 𝑓(𝑛) ∈ 𝑂(𝑔(𝑛)) s’il existe
deux constantes positives n0 et c telle que 𝑓(𝑛) ≤ 𝑐𝑔(𝑛) pour toute valeur de n>n0.

Cette définition implique que g(n) doit être plus grand ou égale à f(n), à une constante
près, lorsque la taille des données augmente. La figure 3 donne une représentation de
cette définition.

Analyse asymptotique 6
f(n) = O(g(n))

cg(n)
f(n)
temps

n0 n

Figure 3 : Définition de la notation Grand-O

Cette notation représente un pire cas puisque la fonction g(n) est pire que la fonction f(n)
à une constante près. Notez que lorsque n est petit, le nombre d’opérations exécutées
n’est pas nécessairement représentatif, c’est pour cette raison que ces valeurs sont
ignorées par la notation Grand-O.

Selon la définition, nous pourrions dire que toutes les fonctions sont en O(n!), une des
pires fonctions possibles. Bien que cela soit vrai selon la définition, ce n’est pas très utile
comme information. Il est important de toujours donner la fonction la plus proche
(« tight ») possible de f(n) afin d’avoir une information intéressante sur le comportement
de l’algorithme.

À titre d’exemple, appliquons la notation Grand-O à l’algorithme suivant :

Algorithme(T[1..n])
1. d=∞
2. pour i = 1 à n
3. pour j = i+1 à n
4. si |T[i] – T[j]| < d
5. d = |T[i] – T[j]|
6. retourner d

Analyse asymptotique 7
La première ligne de l’algorithme est exécutée une seule fois, elles seront donc en O(1)1.
La ligne 2 est exécutée n+1 fois, elle donc en O(n).

En ce qui concerne la ligne 3, nous avons déjà calculé qu’elle était exécutée
"("*%)
!
fois, ce qui est O(n2). Il n’est par contre pas nécessaire de faire le calcul précis pour
déterminer l’ordre du nombre d’exécutions. Il est en effet possible de la déduire en ne
considérant que le pire cas. Pour cette ligne, le pire cas sera lorsque i sera égal à 1, ce qui
donne O(n) opérations. Comme il s’agit d’une boucle imbriquée qui est exécutée O(n)
fois, nous aurons un total de O(n2) exécutions.

Les lignes 4 et 5 seront aussi exécutées O(n2) fois. La ligne 6 est quant à elle exécutée une
seule fois, elle est donc en O(1).

Au total, nous avons donc O(1)+O(n)+O(n2)+O(n2)+O(n2)+O(1) = O(n2) puisque seul le


monôme avec le degré le plus élevé est conservé et que les constantes sont éliminées. Il
s’agit donc d’un algorithme en O(n2).

Voici un autre exemple avec un algorithme un peu plus complexe et pour lequel nous
n’avons pas fait l’analyse détaillée. Cet algorithme permet de déterminer la sous-
séquence de nombres consécutifs, dans un tableau, dont la somme est maximale. Par
exemple, pour T = [-2,11,-4,13,-5,1], l’algorithme retournera 20 (11-4+13).

maxSubSum1(T[1..n])
1. maxSum = 0
2. pour i = 1 à n
3. pour j = i à n
4. sum = 0
5. pour k = i à j
6. sum = sum + T[k]
7. si sum > maxSum
8. maxSum = sum
9. retourner maxSum

La ligne 1 de l’algorithme est exécutée une seule fois, elle donc en O(1). La ligne 2 est
exécutée n+1 fois, elle est donc en O(n).

1
O(1) est utilisé pour représenter un nombre constant d’opérations, donc qui est indépendant de la taille des
données d’entrée.

Analyse asymptotique 8
La ligne 3, dans le pire cas, est exécutée aussi n+1 fois (lorsque i=1), elle est donc en O(n),
mais boucle est imbriquée dans la boucle de la ligne 2. Donc, au total, elle est exécutée
O(n2) fois.

La ligne 4, est imbriquée dans les boucles des lignes 2 et 4, elle sera donc exécutée O(n2)
fois.

La ligne 5 est une autre boucle. Celle-ci demande un peu plus d’attention. Comme nous
calculons un pire cas, nous allons considérer la pire situation : lorsque i=1 et j=n. Dans
cette situation, la boucle sera exécutée O(n) fois. Comme celle-ci est imbriquée dans les
boucles aux lignes 2 et 3, elle sera exécutée O(n3) fois.

La ligne 6 est exécuté autant de fois que la boucle de la ligne 5 puisqu’elle est à l’intérieur
de celle-ci. Elle est donc exécutée O(n3) fois.

La ligne 7 est quant à elle à l’extérieure de la boucle de la ligne 5, mais à l’intérieur des
boucles des lignes 2 et 3. Cette opération sera donc exécutée O(n2) fois.

Le nombre d’exécutions de la ligne 8 dépend de la condition de la ligne 7. Dans le pire


cas, la condition sera toujours vraie. Donc, cette opération sera exécutée O(n2) fois.

La dernière ligne de l’algorithme est exécutée une seule fois, elle est donc en O(1).

Si on résume, nous avons :

maxSubSum1(T[1..n])
1. maxSum = 0 O(1)
2. pour i = 1 à n O(n)
3. pour j = i à n O(n2)
4. sum = 0 O(n2)
5. pour k = i à j O(n3)
6. sum = sum + T[k] O(n3)
7. si sum > maxSum O(n2)
8. maxSum = sum O(n2)
9. retourner maxSum O(1)

Nous avons donc au total 2*O(n3) + 4*O(n2) + O(n) + 2*O(1) ce qui nous donne O(n3) une
fois simplifié. Cet algorithme est donc en O(n3).

Analyse asymptotique 9
Notation Grand-Oméga

La notation Grand-Oméga (Ω) permet d’expliquer le comportement d’un algorithme, en


fonction de la taille des données d’entrée, dans le meilleur cas possible. Une fonction
𝑓(𝑛) ∈ Ω,𝑔(𝑛)- s’il existe deux constantes positives n0 et c tel que 𝑓(𝑛) ≥ 𝑐𝑔(𝑛) pour
n’importe quelle valeur de n0>0. La figure 4 est une représentation graphique de cette
définition.

f(n) = W(g(n))

f(n)

cg(n)
temps

n0 n

Figure 4 : Définition de la notation Grand-Oméga

Le graphique montre que la fonction g(n) est une borne inférieure de la fonction f(n), à
une constante près. Le meilleur cas possible pour une borne inférieure est une fonction
en Ω(1), c’est-à-dire une fonction dont le nombre d’opérations ne dépend pas de la taille
des données d’entrées. Similairement à la notation Grand-O, le but est de trouver la plus
grande fonction possible qui correspond à la définition afin d’avoir une analyse
comportementale la plus précise possible.

L’exemple le plus classique de l’utilité de cette notation est le tri par insertion :

Analyse asymptotique 10
InsertionSort( T[ 1.. n ] )
1. pour j = 2 à n
2. key = T[j]
3. i = j -1
4. tant que i > 0 et T[i]>key
5. T[i+1] = T[i]
6. i = i-1
7. T[i+1] = key

L’analyse asymptotique avec la notation Grand-O démontre que dans le pire cas,
l’algorithme a une complexité de O(n2). Le pire cas se produit lorsque le tableau est trié
en ordre décroissant, ce qui force l’exécution de la boucle de la ligne 4 un maximum de
fois.

Par contre, que se passe-t-il si le tableau est trié en ordre croissant? Pour les lignes 1 à 3,
l’ordre des éléments dans le tableau ne change rien. Ces lignes sont donc toutes
exécutées. Ω(𝑛) fois.

Par contre, pour la ligne 4, la condition 𝑇[𝑖 ] > 𝑘𝑒𝑦 ne sera jamais vraie si le tableau est
déjà en ordre croissant. La conséquence est donc que la ligne 4 sera exécutée, au total
Ω(𝑛) fois, seulement pour vérifier que la condition est toujours fausse. Puisque les lignes
5 et 6 sont dans cette boucle, elles ne seront jamais exécutées. Elles sont donc en Ω(1).

Finalement, la ligne 7 Ω(𝑛) fois puisqu’elle est dans la boucle de la ligne 1. Pour résumer,
nous avons donc :

InsertionSort( T[ 1.. n ] )

1. pour j = 2 à n Ω(𝑛)
2. key = T[j] Ω(𝑛)
3. i = j -1 Ω(𝑛)
4. tant que i > 0 et T[i]>key Ω(𝑛)

5. T[i+1] = T[i] Ω(1)

6. i = i-1 Ω(1)

7. T[i+1] = key Ω(𝑛)

Analyse asymptotique 11
Pour un grand total de Ω(5𝑛 + 2) = Ω(𝑛). Ce qui veut dire que le tri insertion à un
comportement linéaire lors que les données sont déjà triées. Il s’agit ici d’une information
très importante qu’il faut savoir lorsque vient le temps de sélectionner un algorithme de
tri. En effet, si nous savons que nos données sont presque triées, il pourrait être
avantageux d’utiliser le tri par insertion puisque le comportement sera plus proche du
linéaire que du quadratique.

Notation Grand-Thêta

La notation Grand-Thêta représente une borne très serrée (« tight ») puisqu’une fonction
𝑓(𝑛) ∈ Θ(𝑔(𝑛)) si elle est bornée supérieurement et inférieurement par g(n). Par
conséquent, 𝑓(𝑛) ∈ Θ,𝑔(𝑛)- si 𝑓(𝑛) ∈ O,𝑔(𝑛)- et 𝑓(𝑛) ∈ Ω,𝑔(𝑛)-.

Plus formellement, une fonction 𝑓(𝑛) ∈ Θ,𝑔(𝑛)- s’il existe trois constantes positives n0,
c1 et c2 tels que 𝑐% 𝑔(𝑛) ≤ 𝑓(𝑛) ≤ 𝑐2 𝑔(𝑛) pour n’importe quelle valeur de n0>0. La figure
5 représente graphiquement cette définition.

f(n) = Q(g(n))

c1 g(n)
f(n)
c2 g(n)
temps

n0 n
n0
Figure 5 : Définition de la notation Grand-Thêta

Un algorithme en Θ(𝑔(𝑛)) nous dit que peu importe les données, l’algorithme se
comportera toujours de la même façon, peu importe les données d’entrées. L’algorithme
suivant, que nous avons déjà analysé est un bon exemple :

Analyse asymptotique 12
Algorithme(T[1..n])
1. d=∞
2. pour i = 1 à n
3. pour j = i+1 à n
4. si |T[i] – T[j]| < d
5. d = |T[i] – T[j]|
6. retourner d

La complexité de cet algorithme est en 𝑂(𝑛! ) et en Ω(𝑛! ). Ce qui veut dire que peu
importe les valeurs du tableau T et peu importe leur disposition, l’algorithme se comporte
tout le temps de manière quadratique lors que la taille du tableau augmente.

Par contre, nous avons vu que l’algorithme de tri par insertion est en 𝑂(𝑛! ) dans pire cas,
mais en Ω(𝑛) dans le meilleur cas. Cet algorithme ne peut donc pas être exprimé par la
notation Grand-Thêta. En d’autres termes, pouvoir exprimer un algorithme en notation
Grand-Thêta indique que l’algorithme se comporte de la même façon dans le meilleur et
le pire cas..

Notation petit-o et petit-oméga

Ces deux notations sont parfois utilisées en algorithmique. Voici leur définition :

• Une fonction f(n) ∈ o(g(n)) s’il existe deux constantes positives n0 et c tel que
f(n) < cg(n) pour tout n>n0
• Une fonction f(n) ∈ 𝜔(g(n)) s’il existe deux constantes positives n0 et c tel que
f(n) > cg(n) pour tout n>n0

La différence est que pour la notation petit-o, la fonction f(n) doit être strictement plus
petite que g(n), au lieu d’être plus petite ou égale à g(n) dans la notation Grand-O. La
même relation s’applique entre la notation petit-𝜔 et la notation grand-Oméga.

Comme mentionné, ces deux notations sont parfois rencontrées en algorithmique, mais
elles sont beaucoup plus utilisées en mathématique.

Analyse asymptotique 13
Conclusion
L’analyse asymptotique est un puissant outil permettant d’analyser le comportement de
l’algorithme lorsque la taille des données d’entrées augmente. Cette analyse permet de
comparer différents algorithmes pour résoudre un problème donné afin de déterminer
lequel correspond le mieux à nos besoins. Il peut aussi servir à déterminer si l’algorithme
répond simplement aux requis du logiciel et permet potentiellement de spécifier des
contraintes d’utilisation.

Dans cette introduction, nous avons utilisé l’analyse asymptotique pour calculer la
complexité d’un algorithme. Dans certaines situations, il peut être intéressant de
s’intéresser à une opération en particulier. Par exemple, supposons que la tâche consiste
à trier de très gros textes. Dans cette situation particulière, l’opération la plus importante
sera l’algorithme de comparaison permettant d’ordonner les différents textes. Cette
opération risque d’être longue et par conséquent, il pourrait être intéressant de calculer
l’ordre asymptotique sur le nombre de comparaisons afin de déterminer quel algorithme
se comporte le mieux pour cette opération particulière en fonction de la taille des
données d’entrées.

Analyse asymptotique 14

Vous aimerez peut-être aussi