UVT
Mastère Professionnel - 1ère Année
Ingénierie Avancée des Systèmes Robotises et Intelligence
Artificielle IASRIA -
Algorithmique et Complexité
Auteur: M. Fathallah
Introduction
Déterminer la complexité d’un algorithme, c’est évaluer les ressources nécessaires à son
exécution (essentiellement la quantité de mémoire requise) et le temps de calcul à
prévoir.
La complexité d'un algorithme peut donc être évaluée en temps et en espace :
Complexité en temps : évaluation du temps d’éxecution de l'algorithme
Complexité en espace : évaluation de l'espace mémoire occupé par l’éxecution de
l'algorithme
Ce qui permet de:
Prévoir le temps d’éxecution d'un algorithme
Comparer deux algorithmes réalisant le même traitement
=> Estimation de la performance des algorithmes et identifier le plus efficace
d’entre eux pour le même problème.
Généralités
Comment peut-on comparer les algorithmes ?
Imaginez qu’on a deux algorithmes A1 et A2, les deux peuvent résoudre le même
problème P. Les deux nous donnent des résultats corrects.
La question qui vient en tête, lequel d’entre eux nous allons exploiter ? Le meilleur
? Comment peut-on mesurer l’efficacité d’un algorithme ?
En Algorithmique, on peut comparer les algorithmes selon deux paramètres :
le premier c’est l’espace mémoire, ceci veut dire, combien d’espace mémoire va
être utilisé par l’algorithme pour résoudre le problème.
Le deuxième paramètre est le temps d’exécution. En fait, un algorithme peut
donner le résultat avant l’autre, lorsqu’on les exécute sur la même machine. Il
n’est pas possible de comparer des algorithmes lorsqu’on a pas la même
combinaison matérielles (processeur, bus de la carte mère, vitesse et capacité de
la RAM), car cette dernière influence sur le temps d’exécution.
Pourquoi la complexité …
Un problème Sol1, sol2, sol3 …solN Comparaison et choix => Meilleure
sol
Meilleure sol est l’algorithme le plus efficace
L’efficacité = MOINS de temps d’exécution, MOINS d’espace mémoire.
Objectif: Estimer le «Temps d’exécution» pour comparer plusieurs
algorithmes pour le même problème.
Mesure de la complexité en temps
Définition : Une opération OP est fondamentale pour un algorithme A si le
nombre de OP influe directement sur le temps d’exécution de l’algorithme A.
Exemples d’opérations fondamentales
Algorithme Opérations fondamentales
Recherche d’un élément dans une Comparaison entre l’élément
liste en mémoire centrale cherché et les composants de la liste
Trier une liste d’éléments - Comparaison entre deux éléments
- Déplacement des éléments
Multiplier deux matrices de nombres - Multiplication
- Addition
Complexité temporelle
mesure le nombre d’opérations élémentaires effectuées par
l’algorithme A en fonction de la taille des données n,
indépendamment de la machine et du langage utilisés, exprimé par
CA(n).
La taille des données n correspond à la quantité d’informations
manipulée par l’algorithme. Par exemple, dans le cas des tableaux, n
est égale au nombre D’éléments du tableau, dans le cas des graphes
le nombre n est égal aux nombre de sommets plus le nombre d’arcs,
etc.
Opérations élémentaires : affectations, comparaisons, opérations
arithmétiques, branchement, R/W, appel de sous-programmes, etc.
Objectif de calcul de la complexité : Elle permet en particulier de
comparer deux algorithmes résolvant le même problème.
Paramètres de complexité
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
Pour un même algorithme, différents choix de paramètres de complexité peuvent
être possible. Plusieurs complexités peuvent être calculées, selon les besoins.
Evaluation du temps de calcul (1)
La complexité d'un algorithme ou l'évaluation du temps de calcul d'un
algorithme est une mesure exprimée en fonction de la taille n des données
T(n) = nombre d'opérations élémentaires
On distingue trois sortes de complexités :
la complexité dans le pire des cas : calcul du coût dans le pire des cas,
Tpire (n) = maxn {T(n)}
la complexité en moyenne : on calcule le coût pour chaque donnée possible puis on
divise la somme de ces coûts par le nombre de données différentes,
Tmoyenne (n) = 1/n ∑n{T(n)}
la complexité dans le meilleur des cas : on calcule le coût en se plaçant dans le meilleur
des cas.
Tmeilleur(n) = minn {T(n)}
Evaluation du temps de calcul (2)
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
i <- i+1;
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
Complexité moyenne, au mieux et au pire (1)
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.
Complexité moyenne, au mieux et au pire (2)
fonction avec retour booléen rechercheElement(chaine tab[], chaine x)
entier i; Si x est dans la première case du tableau : 1
booléen b; tour de boucle avec la condition tab[i]=x vraie
Si x est dans la deuxième case du tableau : 1
début
tour de boucle avec la condition tab[i]=x fausse
b <- FAUX;
et 1 tour de boucle avec la condition tab[i]=x
i <- 0; vraie
tantque (i < tab.longueur ET non b) faire ...
si (tab[i] = x) alors Si x est dans la dernière case du tableau :
b <- VRAI; tab.longueur-1 tours de boucle avec la condition
finsi tab[i]=x fausse et 1 tour de boucle avec la
condition tab[i]=x vraie
i <- i+1;
Si x n'est pas dans le tableau : tab.longueur
fintantque
tours de boucle avec la condition tab[i]=x fausse
retourne b;
fin
Complexité moyenne, au mieux et au pire (3)
fonction avec retour booléen rechercheElement(chaine tab[], chaine x)
entier i; n = la taille du tableau, ae = affectation d'entier, ce =
booléen b; comparaison d'entier, oe=opération sur les entiers.
début Complexité au pire (x n'est pas dans le tableau) :
b <- FAUX; 2*ae+n*(2*ce+3*oe+ae) Mq HW
Complexité au mieux (x est dans la première case du tableau) :
i <- 0;
2*ae+2*ce+2*oe Mq HW
tantque (i < tab.longueur ET non b) faire
Complexité en moyenne : considérons qu'on a 50% de chance que
si (tab[i] = x) alors
x soit dans le tableau, et 50% qu'il n'y soit pas, et s'il y est sa
b <- VRAI; position moyenne est au milieu.
finsi
Le temps d'exécution est [ (2*ae+n*(2*ce+3*oe+ae)) +
i <- i+1; (2*ae+(n/2)*(2*ce+3*oe+ae)) ] /2, de la forme a*n+b (avec a
fintantque et b constantes) Mq HW
retourne b;
fin
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
Paramètre de
complexité
0 100000
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
x
f = O(g) signifie que f est dominée asymptotiquement par g ou que g domine asymptotiquement f.
Algorithmique et Programmation
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(g1+g2)
- si f1=O(g1) et f2=O(g2) alors f1*f2 = O(g1*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
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
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
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
Application: 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)
Algorithmique et Programmation
Complexité en moyenne : considérons qu'on a 50% de chance que x soit dans le tableau, et 50% qu'il
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é
ln(n) n n² 2n
flops
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é.
Calcul de complexité (1)
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.
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 resultat <- resultat*i; (n-1) * (ae + oe)
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é.
Temps d’exécution d’un algorithme (1)
Dépend des facteurs suivants:
Lesdonnées utilisées par le programme (taille des
données) ;
La qualité du code généré par le compilateur (langage
utilisé) ;
La machine utilisée (vitesse, mémoire,. . .) ;
La complexité de l’algorithme lui-même (nombre
d’instructions).
Temps d’exécution d’un algorithme (2)
Exemple :
Soit à calculer les temps d’exécution t1, t2 correspondant
aux complexités O(n) et O(n2) sur des données de taille
106, avec un processeur ayant une puissance d’exécution
109 Flops.
t1= 106 / 109 = 10-3 s ; t2= 1012 / 109 = 103 s=3h.
On peut conclure que la complexité O(n) est bonne, par
contre la complexité O(n2) est mauvaise.