UNIVERSITE ABDELHAMID IBN BADIS MOSTAGANEM
FACULTE DES SCIENCES EXACTES ET DE L’INFORMATIQUE
DEPARTEMENT DE MATHEMATIQUES ET INFORMATIQUE
Complexité des algorithmes
Cours 7 ASD
2018-2019
Analyse d’un algorithme
L’analyse d’un algorithme permet de le juger sur la
base de plusieurs critères :
Est-ce qu'il fait ce qu'on lui demande de faire ?
(correction)
Est-ce qu’il le fait en un temps raisonnable
?(performance)
Ou encore:
Est-ce qu'il est muni d'une documentation expliquant
comment il fonctionne ?
Est-ce que le code est lisible ?
Étudier la complexité d'un algorithme fait partie
d'une démarche globale d'analyse et de mesure
des performances d'un algorithme.
2
Théorie de la complexité
La théorie de la complexité (algorithmique) a pour
objectifs de :
évaluer les performances des algorithmes
classer les algorithmes selon leur efficacité
comparer les algorithmes résolvant un problème donné
et de choisir le plus adéquat sans avoir besoin de les
implémenter
3
Analyse de la complexité
L’analyse de la complexité revient à quantifier les
2 grandeurs physiques :
temps d’exécution (critère temporel)
Espace mémoire (critère spatial)
Ces deux critères sont en relation directe avec
les performances d'un algorithme
4
Complexité d’un programme
L'efficacité d'un algorithme peut être évaluée à
travers l’implémentation sur ordinateur d’un
programme correspondant.
Le programme va s'exécuter en un temps fini et
va forcément mobiliser des ressources pendant
son exécution.
Le complexité d'un programme est définie à
travers :
la quantité de mémoire nécessaire à son exécution
complexité spatiale
la quantité de temps nécessaire pour son exécution du
début jusqu'à la fin complexité temporelle
5
Inconvénients
Les mesures de performances étant dépendantes :
du processeur utilisé,
des temps d'accès à la mémoire vive et au disque
dur,
du langage de programmation,
de la qualité du code produit par le compilateur
utilisé,
etc.
Ces mesures risquent de varier selon les puissances
des machines
de plus, une telle démarche rendrait difficile la
comparaison des algorithmes entre eux.
6
Comment quantifier ?
Une approche indépendante de la machine sur laquelle
s’exécute l’algorithme et de la traduction de
l’algorithme en langage exécutable par la machine est
nécessaire pour évaluer l'efficacité des algorithmes.
Le but étant d’établir des résultats plus généraux
permettant:
d’estimer l’efficacité intrinsèque de l’algorithme (sans avoir
besoin de l’implémenter au préalable).
de comparer entre eux les algorithmes indépendamment de
la machine sur laquelle ils sont implémentés
7
Détermination de la
complexité temporelle d’un
algorithme
8
Complexité temporelle
On peut calculer la complexité selon les deux
paramètres "temps d'exécution" et "place
mémoire" pour comparer l'efficacité des
algorithmes
Cependant la complexité temporelle est la plus
utilisé pour comparer les algorithmes, c’est pour
cela que nous allons nous intéresser dans ce cours
uniquement à la complexité temporelle.
Dans la suite de ce cours, lorsqu’on parlera de
complexité, il s’agira de complexité temporelle.
9
Mesure de la complexité temporelle
La mesure de la complexité temporelle ne peut pas
s'appuyer sur une unité de temps (par exemple la
milliseconde) car ce genre d'unité est étroitement
lié aux caractéristiques techniques de
l'ordinateur.
En effet, il est important que l'unité utilisée
reflète la complexité de l'algorithme utilisé
indépendamment de la machine sur laquelle le
programme s'exécute
il faut donc utiliser des unités de temps
abstraites
Comment?
10
De quoi dépend la mesure de la complexité ?
A priori, on peut distinguer 2 facteurs
déterminants pour le calcul de la complexité en
temps d’un algorithme :
1. Une ou plusieurs opérations élémentaires
pertinentes par rapport au problème, au sens où
le temps d’exécution d’un algorithme résolvant
ce problème est toujours proportionnel au
nombre de ces opérations. On les désignera par
opérations fondamentales.
2. La taille des entrées.
11
Taille des entrées
En pratique, pour évaluer la taille des données
d’un algorithme on choisit la ou les dimensions
les plus significatives.
Par exemple:
dans le cas de la recherche dans un tableau ou
le tri d’un tableau : la taille est le nombre
d’éléments du tableau,
dans le cas du produit de 2 matrices carrées :
la taille est la dimension de la matrice.
12
Détermination des opérations fondamentales
La nature du problème fait que certaines
opérations sont fondamentales et que leur
nombre intervient principalement dans l’étude de
la complexité de l’algorithme.
Exemple d’opérations fondamentales :
Pour la recherche d’un élément e dans un tableau :
le nombre de comparaisons entre l’élément e et
les entrées du tableau;
Pour le tri d’un tableau, on peut considérer 2
opérations fondamentales : le nombre de
comparaisons et le nombre de déplacements;
Pour la multiplication de 2 matrices : le nombre de
multiplications et le nombre d’additions.
13
Calcul du nombre d’opérations fondamentales
Après avoir déterminé les opérations fondamentales, il
faut compter le nombre d’opérations de chaque type.
S’il existe plusieurs opérations fondamentales, il
faudra les décompter séparément.
Précisons qu’il n’existe pas de système complet de
règles permettant de compter le nombre d’opérations
en fonction de la syntaxe des algorithmes. On peut
cependant citer quelques règles.
14
Règle 1 : nombre d’opérations d’une séquence
Lorsque les opérations sont dans une séquence
d’instructions S, leur nombre s’ajoutent.
Si on note par TS le nombre d’opérations de la
séquence S d’instructions Ij, on a :
TI1+I2+…+Im = TI1 + TI2 + … + TIm
15
Règle 2 : nombre d’opérations d’une alternative
Comme il est difficile, voire impossible, de
déterminer la branche de l’alternative qui est
exécutée, la règle généralement adoptée consiste à
majorer le nombre d’opérations par le nombre
maximum d’opérations entre les 2 branches.
Si A est une alternative de la forme :
if condition I1 else I2
Alors :
TA = Tcondition + max (TI1,TI2)
16
Règle 3 : nombre d’opérations d’une boucle
Le nombre d’opérations dans la boucle est :
TI(i) avec,
i : variable de contrôle de la boucle
TI(i) : nombre d’opérations fondamentales
lors de l’exécution de la ième itération.
Pour évaluer les bornes de i, il faut connaître
le nombre d’itérations.
il doit être calculé à partir de l’algorithme. Ce
calcul peut s’avérer difficile. On se contente donc
d’un majorant.
17
Détermination de la complexité (1)
Nous allons analyser une fonction de recherche séquentielle
d’un double x dans un tableau v. Cette fonction renvoie le
rang i de x si xv et -1 si xv.
#define N 100
double v[N];
int recherche_Sequentielle (double x)
{
int i;
i = 0;
while ((i < N) && (v[i] != x))
i++;
if(i < N) return i;
return -1;
}
18
Exemple : Détermination de la complexité (2)
Opération fondamentale: comparaison des
éléments du tableau à x
Taille des données: N (le nombre d’éléments
contenus dans le tableau)
Calcul de la complexité :
la complexité de la fonction dépend du nombre
d'itérations.
chaque itération comporte une seule comparaison mais
le nombre d'itération (ou de comparaison) n'est pas
connue à l'avance, il dépend de la position de «
x » dans le tableau.
19
Exemple : Détermination de la complexité (3)
Le nombre d’itérations est égal à :
N (taille du tableau) : si x n’est pas dans le tableau
i : rang de la ière occurrence de x si x est dans le
tableau.
Ce nombre peut donc être majoré par N qui
représente le nombre d'itération maximum.
La complexité de la fonction est donc N
comparaisons entre deux valeurs de type "double"
20
Détermination de la complexité
L’exemple d’analyse d’algorithme met en évidence
2 points essentiels :
1. Le choix du (ou des) l’opération(s) que l’on prend en
compte doit être établi avant toute analyse et précisé
dans le résultat.
2. La complexité dépend de la taille des données (ici N)
et, pour une taille fixée, des différentes entrées
possibles.
Dans le cas de l’exemple, pour les données de
taille N, la complexité en nombre de comparaisons
varie de 1 à N selon les données x et v :
Les données où x apparaît au rang i de v correspondent à
une complexité i,
Celles où x n’apparaît pas dans v, correspondent à une
complexité N.
21
Complexité en moyenne, au mieux et au pire
L’exemple de la recherche séquentielle a mis en
évidence , que le temps d’exécution d’un algorithme
dépend de la donnée sur laquelle il opère. Plusieurs
mesures peuvent donc être nécessaires pour analyser
le comportement d’un algorithme sur un ensemble de
données de taille fixée. On s’intéresse à 3 mesures :
La complexité dans le meilleur des cas,
La complexité dans le pire des cas,
La complexité en moyenne.
22
La complexité dans le meilleur des cas
La complexité dans le meilleur des cas, d’un algorithme
A sur l’ensemble Dn des différentes données possibles
de taille N, donne une indication sur la borne minimale
de la complexité de A sur les données de taille N.
Elle est déterminée sur la base d’une construction de
données d particulière correspondant à la
configuration la plus favorable pour la résolution de
l’algorithme A.
TminA(N) = min { TA(d) ; d DN }
Pour l’exemple de la recherche séquentielle, la
complexité minimale est 1. Elle correspond au jeu de
données d qui est tel que x apparaît au rang 1 de v.
23
La complexité dans le pire des cas
La complexité dans le pire des cas, d’un algorithme A
sur l’ensemble DN des différentes données possibles
de taille N, donne une borne supérieure du temps
d’exécution de A.
Elle est particulièrement utile car elle donne une
estimation maximale des données qui pourront être
traitées par A.
TmaxA(N) = max { TA(d) ; d DN }
Pour l’exemple de la recherche séquentielle, la
complexité maximale est de N. Elle correspond au jeu
de données d qui est tel que x n’apparaît pas dans v.
24
La complexité en moyenne
Les cas extrémaux ne sont pas forcément les plus
fréquents. Parfois, on cherche à savoir quel est « en
général » le comportement de l’algorithme. Cette
information est fournie par la complexité en moyenne.
TmoyA (n) prob(d).T (d)
dDN
A
Où prob(d) est la probabilité que l’on ait la donnée d en
entrée de l’algorithme.
La complexité en moyenne est souvent difficile à
calculer car il n’est pas toujours facile de déterminer
un modèle de probabilité adéquat au problème.
L’analyse est mathématiquement difficile.
25
Dans les faits….
En réalité, la complexité d'un algorithme est une
mesure de sa performance asymptotique dans le
pire cas.
Pourquoi il est préférable de choisir le pire cas ?
L'idée est de s'intéresser aux performances de
l'algorithme dans les situations où le problème prend le
plus de temps à résoudre.
Que signifie asymptotique….
26
Notion d’ordre de grandeur
27
Complexité et taille des données
Pour traiter un problème de petite taille la
méthode employée importe peu, alors que pour un
problème de grande taille, les différences de
performance entre algorithmes peuvent être
énormes.
Il est donc important de connaître la rapidité de
croissance de la complexité d’un algorithme
lorsque la taille des données augmente.
Ce qui nous intéresse c'est de voir comment
évolue la complexité en fonction de la taille des
données et en particulier lorsque ces données
deviennent très grandes.
28
Asymptotique
Asymptotique signifie donc qu'on s'intéresse à
des données très grandes
Exemple de comportement asymptotique
soit
1. un problème à résoudre sur des données de
tailles n et
2. deux algorithmes résolvant de problème en un
temps f1(n) et f2(n) respectivement
lequel faut-il choisir?
29
Exemple de comportement asymptotique
f1(n) f2(n)
25
20
TEMPS D’EXÉCUTION
15
10
0
5 10
TAILLE DES DONNÉES
La courbe verte semble correspondre à un algorithme plus efficace
30
Exemple de comportement asymptotique
f1(n) f2(n)
1200000
1000000
TEMPS D’EXÉCUTION
800000
600000
400000
200000
0
5 10 15 20
TAILLE DES DONNÉES
La courbe verte semble correspondre à un algorithme plus
efficace ... mais seulement pour de petites valeurs
31
La notation en O de la complexité
Les calculs à effectuer pour évaluer le temps
d'exécution peuvent parfois être longs et pénibles
Pour éviter cela, il faut avoir recours à une
approximation de ce temps de calcul notation
de Bachmann Landau (grand O)
32
Notation de Bachmann Landau : « grand O »
On dit qu'une fonction f est dominée par une
fonction g et on écrit f = O(g), s'il existe c et n0
tel que:
f = O(g) c R+*, n0 N, tels que : n > n0, f(n) c.g(n)
Cette notion donne un majorant de l'ordre de
grandeur de f
Autrement dit, f(n) est en O(g(n)) s'il existe un
seuil à partir duquel la fonction f() est toujours
dominée par la fonction g() à une constante
multiplicative fixée près
33
Domination asymptotique
f = O(g) signifie que f est dominée asymptotiquement par g
34
Règle de calcul de la complexité
Avec la notation en grand O, on calcule le temps
d'exécution comme avant mais on y apportant des
simplifications:
On ne considère pas les constantes multiplicative (elles
valent 1)
On annule les constantes additives (elles valent 0)
On ne retient que les termes dominants
35
Notation en grand O : Exemple
36
Quelques classes de complexité
Les fonctions qui correspondent aux ordres de grandeurs les plus
fréquemment rencontrés dans les calculs de complexité sont
présentées par ordre croissant dans le tableau ci-dessous.
fonction Nom
1 Constante (ne dépend pas de la taille des
données)
log2(n) logarithmique
n linéaire
nlog2(n) n logarithmique
n2 Quadratique ou polynomiale d’ordre 2
n3 Cubique ou polynomiale d’ordre 3
37
Complexité Cas de programmes typiques à la catégorie
Toutes les instructions sont exécutées une seule
1 fois quelle que soit la taille n du problème.
La taille du problème est divisée par une constante à
log(n) chaque itération.
Une boucle de 1 à n dont le corps effectue un
n travail de durée constante et indépendante de n.
A chaque itération la taille du problème est divisée par
nlog(n) une constante avec à chaque fois un parcours linéaire
des données.
Deux boucles imbriquées chacune allant de 1 à n et
n2 corps de la boucle interne constant.
n3 Mêmes cas que n2 avec trois boucles imbriquées.
38
Quelques exemples d’applications (1)
Donner la complexité algorithmique des algorithmes
suivants:
int FindMaxElement (int T[], int n){
int max = T[0]; Opération fondamentale :
comparaison
for(int i = 0; i < n; i++) Nombre de comparaison par
{ itération de boucle : 1
Nombre d'itération de boucle:
if(max< T[i]) n
max = T[i]; Complexité O(n) complexité
linéaire
}
return max;
}
39
Quelques exemples d’applications (2)
int FindInversions (int v[], int n){
int inversions =0;
for (int i =0 ; i<n; i++)
opération fondamentale :
for (int j = i+1; j<n; j++) comparaison
if(v[i] > v[j]) Nombre de comparaison par
itération de boucle : 1
inversions++; Nombre d'itération de boucle
n * (n-1)/2
return inversions; Complexité O(n2) complexité
quadratique
40