Complexité algorithmique
Objectifs des calculs de complexité :
- pouvoir prévoir le temps d'exécution d'un algorithme
- pouvoir comparer deux algorithmes réalisant le même traitement
In almost every computation a great variety of arrangements for the
succession of the processes is possible, and various considerations must
influence the selection amongst them for the purposes of a Calculating Engine.
One essential object is to choose that arrangement which shall tend to reduce
to a minimum the time necessary for completing the calculation.
Ada Lovelace (1815-1852) - Notes on the Sketch of The Analytical Engine.
Algorithmique et Programmation 1
Complexité en temps et en espace
Exemple : échange de deux valeurs entières
// échange des valeurs de deux variables x et y
entier x, y, z;
... // initialisation de x et y
z <- x;
x <- y;
y <- z;
// échange des valeurs de deux variables x et y
entier x, y;
... // initialisation de x et y
x <- y-x;
y <- y-x;
x <- y+x;
- la première méthode utilise une variable supplémentaire et réalise 3
affectations
- la deuxième méthode n'utilise que les deux variables dont on veut échanger
les valeurs, mais réalise 3 affectations et 3 opérations
Algorithmique et Programmation 2
Espace-temps informatique
La complexité d'un algorithme peut être évalué en temps et en espace :
- complexité en temps : évaluation du temps d'exécution de l'algorithme
- complexité en espace : évaluation de l'espace mémoire occupé par
l'exécution de l'algorithme
Règle (non officielle) de l'espace-temps informatique : pour gagner du temps de
calcul, on doit utiliser davantage d'espace mémoire.
On s'intéresse essentiellement à la complexité en temps (ce qui n'était pas
forcément le cas quand les mémoires coutaient cher)
Algorithmique et Programmation 3
Paramètre de complexité (1/3)
Le paramètre de la complexité est la donnée du traitement qui va (le plus) faire
varier le temps d'exécution de l'algorithme.
Exemple : calcul de la factorielle
fonction avec retour entier factorielle(entier n)
entier i, resultat;
début
resultat <- 1;
pour (i allant de 2 à n pas 1) faire
resultat <- resultat*i;
finpour
retourne resultat;
fin
Le paramètre de complexité est la valeur de n
Exemple : multiplication de deux entiers
fonction avec retour entier multi(entier n, entier m)
début
retourne n*m;
fin
Paramètre de complexité?
Algorithmique et Programmation 4
Paramètre de complexité (2/3)
Exemple : multiplier tous les éléments d'un tableau d'entiers par un entier donné
fonction sans retour multiplie(entier tab[], entier x)
entier i;
début
pour (i allant de 0 à tab.longueur-1 pas de 1)
faire
tab[i] <– tab[i] * x;
finpour
fin
Paramètre de complexité?
Exemple : somme des premiers éléments de chaque ligne d'un tableau à deux
dimensions
fonction avec retour entier sommeTeteLigne(entier tab[][])
entier i,s;
début
s <- 0;
pour (i allant de 0 à tab[0].longueur-1 pas de 1) faire
s <– s + tab[0][i];
finpour
retourne s;
fin
Paramètre de complexité?
Algorithmique et Programmation 5
Paramètre de complexité (3/3)
Pour un même algorithme, différents choix de paramètre de complexité peuvent
être possible. Plusieurs complexités peuvent être calculées, selon les besoins.
Exemple : fusion de deux tableaux
fonction avec retour entier[] fusion(entier tab1[], entier tab2[])
entier i, t[tab1.longueur+tab2.longueur];
début
pour (i allant de 0 à t.longueur-1 pas de 1) faire
si (i<tab1.longueur) alors
t[i] <– tab1[i];
sinon
t[i] <- tab2[i-tab1.longueur];
finsi
finpour
retourne t;
fin
Paramètre de complexité?
Algorithmique et Programmation 6
Calcul de complexité (1/2)
L’idée est d’évaluer le temps de calcul indépendamment de toute implémentation
et de tout contexte d’exécution.
Exemple : la factorielle, le paramètre de complexité est la valeur de n
fonction avec retour entier factorielle1(entier n)
entier i, resultat;
début
resultat <- 1;
pour (i allant de 2 à n pas 1) faire
resultat <- resultat*i;
finpour
retourne resultat;
fin
On doit fixer des temps d'exécution constants à chaque type d'instruction :
- affectation d'entier : ae
- comparaison d'entier : ce
- opération élémentaire sur des entiers : oe
On peut négliger le coût des déclarations, des affectations et du retour.
Algorithmique et Programmation 7
Calcul de complexité (2/2)
On fait la somme du temps d’exécution de toutes les instructions :
resultat <- 1; ae
pour (i allant de 2 à n pas 1) faire (n-1) * (ae + oe)
resultat <- resultat*i;
finpour
Au total, le temps d'exécution sera de la forme a*n+b avec n comme
paramètre de complexité et a et b des constantes.
Il n’est pas possible de fixer des valeurs numériques aux constantes, qui
dépendent du langage d’implémentation et du contexte d’exécution.
On se contente donc de déterminer la forme générale de la complexité.
Algorithmique et Programmation 8
Complexité au mieux et au pire (1/4)
Exemple : recherche séquentielle dans un tableau de n chaines de caractères
fonction avec retour booléen rechercheElement(chaine tab[], chaine x)
entier i;
booléen b;
début
b <- FAUX;
i <- 0;
tantque (i < tab.longueur ET non b) faire
si (tab[i] = x) alors
b <- VRAI;
finsi
fintantque
retourne b;
fin
Le paramètre de complexité est la taille du tableau d'entrée, n.
Le nombre de tours de boucles varie selon que x est dans le tableau ou pas, et
selon l'endroit où x est présent.
Algorithmique et Programmation 9
Complexité au mieux et au pire (2/4)
fonction avec retour booléen rechercheElement(chaine tab[], chaine x)
entier i;
booléen b;
début
b <- FAUX;
i <- 0;
tantque (i < tab.longueur ET non b) faire
si (tab[i] = x) alors
b <- VRAI;
finsi
fintantque
retourne b;
fin
Si x est dans la première case du tableau : 1 tour de boucle avec la condition
tab[i]=x vraie
Si x est dans la deuxième case du tableau : 1 tour de boucle avec la condition
tab[i]=x fausse et 1 tour de boucle avec la condition tab[i]=x vraie
...
Si x est dans dernière case du tableau : tab.longueur-1 tours de boucle avec la
condition tab[i]=x fausse et 1 tour de boucle avec la condition tab[i]=x vraie
Si x n'est pas dans le tableau : tab.longueur tours de boucle avec la condition
tab[i]=x fausse
Algorithmique et Programmation 10
Complexité au mieux et au pire (3/4)
Lorsque, pour une valeur donnée du paramètre de complexité, le temps
d'exécution varie selon les données d'entrée, on peut distinguer :
- La complexité au pire : temps d'exécution maximum, dans le cas le plus
défavorable.
- La complexité au mieux : temps d'exécution minimum, dans le cas le plus
favorable (en pratique, cette complexité n'est pas très utile).
- La complexité moyenne : temps d'exécution dans un cas médian, ou moyenne
des temps d'exécution.
Le plus souvent, on utilise la complexité au pire, car on veut borner le temps
d'exécution.
Algorithmique et Programmation 11
Complexité au mieux et au pire (4/4)
fonction avec retour booléen rechercheElement(chaine tab[], chaine x)
entier i;
booléen b;
début
b <- FAUX;
i <- 0;
tantque (i < tab.longueur ET non b) faire
si (tab[i] = x) alors
b <- VRAI;
finsi
fintantque
retourne b;
fin
n = la taille du tableau, ae = affectation d'entier, ce = comparaison d'entier,
oe=opération sur les entiers.
Complexité au pire (x n'est pas dans le tableau) : ae+n*(2*ce+oe+ae)
Complexité au mieux (x est dans la première case du tableau) : ae+2*ce
Complexité en moyenne : considérons qu'on a 50% de chance que x soit dans le
tableau, et 50% qu'il n'y soit pas, et s'il y est sa position moyenne est au milieu.
Le temps d'exécution est [ (ae+n*(2*ce+oe+ae)) + (ae+(n/2)*(2*ce+oe+ae)) ] /2,
de la forme a*n+b (avec a et b constantes)
Algorithmique et Programmation 12
Complexité asymptotique
Les complexités algorithmiques théoriques sont toujours des approximations :
Première approximation : on ne calcule que la forme générale de la complexité
Deuxième approximation : on ne considère souvent que la complexité au pire
Troisième approximation : on ne regarde que le comportement asymptotique de
la complexité car le temps de calcul ne pose pas de problème lorsqu’on traite des
données peu volumineuses.
temps de
calcul
0 100000 paramètre de
complexité
Algorithmique et Programmation 13
Notation de Landau (1/2)
f et g étant des fonctions, f = O(g) s'il existe des constantes c>0 et n0 telles que
f(x) < c*g(x) pour tout x > n0
c*g(x)
f(x)
n0 x
f = O(g) signifie que f est dominée asymptotiquement par g ou que g domine
asymptotiquement f.
Algorithmique et Programmation 14
Notation de Landau (2/2)
La notation O, dite notation de Landau, vérifie les propriétés suivantes :
- si f=O(g) et g=O(h) alors f=O(h)
- si f=O(g) et k un nombre, alors k*f=O(g)
- si f1=O(g1) et f2=O(g2) alors f1+f2 = O(g2+g2)
- si f1=O(g1) et f2=O(g2) alors f1*f2 = O(g2*g2)
Exemples de domination asymptotique :
x = O(x2) car pour x>1, x<x2
x2 = O(x3) car pour x>1, x2<x3
100*x = O(x2) car pour x>100, x<x2
ln(x) = O(x) car pour x>0, ln(x)<x
si i>0, xi = O(ex) car pour x tel que x/ln(x)>i, xi<ex
Notation Ω : f = Ω(g) s'il existe des constantes c>0 et n0 telles que f(x) ≥ c*g(x)
pour tout x ≥ n0
Algorithmique et Programmation 15
Equivalence asymptotique
f et g étant des fonctions, f = Θ(g) s'il existe des constantes c1, c2, strictement
positives et n0 telles que c1*g(x) ≤ f(x) ≤ c2*g(x) pour tout x ≥ n0
c2*g(x)
f(x)
c1*g(x)
n0 x
Algorithmique et Programmation 16
Classes de complexité (1/2)
O(1) : complexité constante, pas d'augmentation du temps d'exécution quand le
paramètre croit
O(log(n)) : complexité logarithmique, augmentation très faible du temps
d'exécution quand le paramètre croit.
Exemple : algorithmes qui décomposent un problème en un ensemble de
problèmes plus petits (dichotomie).
O(n) : complexité linéaire, augmentation linéraire du temps d'exécution quand le
paramètre croit (si le paramètre double, le temps double).
Exemple : algorithmes qui parcourent séquentiellement des structures linéaires.
O(nlog(n)) : complexité quasi-linéaire, augmentation un peu supérieure à O(n).
Exemple : algorithmes qui décomposent un problème en d'autres plus simples,
traités indépendaments et qui combinent les solutions partielles pour calculer la
solution générale. Tris fusion et rapide.
Algorithmique et Programmation 17
Classes de complexité (2/2)
O(n2) : complexité quadratique, quand le paramètre double, le temps d'exécution
est multiplié par 4.
Exemple : algorithmes avec deux boucles imbriquées. Tris à bulle, par insertion et
par sélection.
O(ni) : complexité polynomiale, quand le paramètre double, le temps d'exécution
est multiplié par 2i.
Exemple : algorithme utilisant i boucles imbriquées.
O(in) : complexité exponentielle, quand le paramètre double, le temps d'exécution
est élevé à la puissance 2.
O(n!) : complexité factorielle, asymptotiquement équivalente à nn
Les algorithmes de complexité polynomiale ne sont utilisables que sur des
données réduites, ou pour des traitements ponctuels (maintenance, ...).
Les algorithmes exponentiels ou au delà ne sont pas utilisables en pratique.
Algorithmique et Programmation 18
Complexité algorithmique et puissance des machines
Temps de calcul pour des données de taille 1 million en fonction de la puissance
de la machine (en flops) et de la complexité de l’algorithme
complexité
flops
ln(n) n n² 2n
106 0,013ms 1s 278 heures 10000 ans
109 0,013μs 1ms ¼ heure 10 ans
1012 0,013ns 1μs 1s 1 semaine
Loi de Moore (empirique) : à coût constant, la rapidité des processeurs double
tous les 18 mois (les capacités de stockage suivent la même loi).
Constat : le volume des données stockées dans les systèmes d'informations
augmente de façon exponentielle.
Conclusion : il vaut mieux optimiser ses algorithmes qu'attendre des années
qu'un processeur surpuissant soit inventé.
Algorithmique et Programmation 19
Complexité de la recherche séquentielle
fonction avec retour booléen rechercheElement(chaine tab[], chaine x)
entier i; booléen b;
début
b <– FAUX; i <- 0;
tantque (i < tab.longueur ET non b) faire
si (tab[i] = x) alors
b <- VRAI;
finsi
i <- i + 1;
fintantque
retourne b;
fin
Le paramètre de complexité est la longueur du tableau, qu'on appelle n.
n = la taille du tableau, a = affectation, c = comparaison o = opération
Complexité au pire (x n'est pas dans le tableau) : 2*a+n*(2*c+a+3*o) = O(n)
Complexité au mieux (x est dans la case 0 du tableau) : 2*a+2*c +2*o = O(1)
Complexité en moyenne : considérons qu'on a 50% de chance que x soit dans le
tableau, et 50% qu'il n'y soit pas, et, s'il y est sa position moyenne est au milieu.
Le temps d'exécution est [ (2*a+n*(2*c+a+3*o)) + (2*a+(n/2)*(2*c+a+3*o)) ] /2, de
la forme α*n+β (avec α et β constantes) = O(n)
Algorithmique et Programmation 20
Complexité de la recherche dichotomique
fonction avec retour booléen rechercheDicho(chaine tab[], chaine x)
entier i, j; booléen b;
début
b <- FAUX; i <- 0; j <- tab.longueur-1;
tantque (i <= j ET non b) faire
si (tab[(j+i)/2] = x)
alors b <- VRAI;
sinon
si (tab[(j+i)/2] > x) alors j <- (j+i)/2 - 1;
sinon i <- (j+i)/2 + 1;
finsi
finsi
fintantque
retourne b;
fin
Le paramètre de complexité est la longueur du tableau, qu'on appelle n.
Cas au pire : x n'est pas dans le tableau.
La longueur de la partie du tableau comprise entre i et j est d'abord n, puis n/2,
puis n/4, ...., jusqu'à ce que n/2t = 1. Le nombre de tours de boucles est donc un
entier t tel que n/2t = 1 soit 2t = n soit t*log(2) = log(n) soit t = log2(n)
Complexité au pire : 3*a + o + (9*o + 3*c + a)*log2(n) = O(log(n))
Algorithmique et Programmation 21
Complexité du tri à bulle
fonction sans retour triBulle(entier tab[])
entier i,j,temp;
début
pour (i allant de tab.longueur-2 à 1 pas -1) faire
pour (j allant de 0 à i pas 1) faire
si (tab[j] > tab[j+1]) alors
temp <- tab[j]; tab[j] <- tab[j+1]; tab[j+1] <- temp;
finsi
finpour
finpour
fin
Le paramètre de complexité est la longueur du tableau, qu'on appelle n.
Cas au pire : la conditionnelle est toujours vraie, c'est-à-dire que les éléments
sont triés dans l'ordre inverse de celui voulu
Complexité au pire : remonter le premier élément nécessite (n-1) tours de la
boucle imbriquée, remonter le deuxième nécessite (n-2) tours, etc. Donc le
nombre de tours total est (n-1) + (n-2) + … + 1 = n*(n-1)/2 soit O(n2).
Algorithmique et Programmation 22