Vous êtes sur la page 1sur 7

Karine Deschinkel

2007/2008

CHAPITRE 6 : COMPLEXITE D’UN ALGORITHME

1 INTRODUCTION

Le temps d’exécution d’un programme dépend de plusieurs facteurs :

Les données entrant dans le programme

La qualité du code binaire engendré par le compilateur à travers l’analyse du code source

La nature et la vitesse d’exécution des instructions du micro- processeur utilisé

La complexité algorithmique du programme (Attention : il ne faut pas confondre la complexité théorique d’un problème qui mesure la difficulté à trouver un algorithme de résolution efficace avec la complexité algorithmique qui mesure son efficacité. Ces deux complexités sont complémentaires. Toutefois la complexité d’un problème est beaucoup plus difficile à démontrer.)

L’exécution d’un programme est mesurée en fonction de l’utilisation des ressources de l’ordinateur :

Le temps de calcul pour exécuter les opérations

L’occupation mémoire pour contenir et manipuler les programmes ainsi que leurs données

En programmation séquentielle, il s’agit souvent de trouver le meilleur compromis entre l’utilisation de ces deux ressources principales. L’analyse de la complexité a pour objectif de quantifier les deux grandeurs physiques citées ci-dessus (temps d’exécution, place mémoire) dans le but de comparer différents algorithmes qui résolvent le même problème, indépendamment de la machine, du langage de programmation, du compilateur et des détails d’implémentation. Le résultat souhaité est d’arriver à l’assertion : sur toutes les machines, quelque soit le langage de programmation, l’algorithme A1 est meilleur que l’algorithme A2 pour les données de grande taille.

2 ELÉMENTS DE BASE DE CALCUL DE COMPLEXITÉ

2.1 Complexité en temps

2.1.1 Opérations fondamentales

Il s’agit de mettre en évidence une ou plusieurs opérations de base (fondamentales) de telle sorte que l’on puisse exprimer le temps des algorithmes en fonction (proportionnellement à) ce nombre d’opérations. On peut se poser la question : à quel niveau doit-on considérer qu’un opération est fondamentale : binaire, instruction, machine, langage de programmation ? Quelles opérations d’un programme doit-on considérer comme fondamentales ? Pour un tri, on peut considérer que les

Karine Deschinkel

2007/2008

comparaisons entre deux éléments et les déplacements des éléments comme fondamentaux. Pour une application de base de données, les entrées et sorties sur disque peuvent être fondamentales. Pour un calcul d’algèbre linéaire, les multiplications et additions sont fondamentales. Il n’y a donc pas de règle absolue de mesure. Il convient de bien préciser à quel niveau d’abstraction on se place et quelles sont les opérations fondamentales considérées.

2.1.2 Règle de calcul du nombre d’opérations de base

Il suffit de compter le nombre d’opérations de chaque type :

Les nombres d’opérations des séquences d’instructions (séparées par les ;) s’ajoutent.

Exemple 1 :

A=0 ; (1 op) C=0 ; (1 op) A=A+1 ; (2 ops: 1 addition + 1 affectation) B=A+C; (2 ops) Au total, 6 opérations

Exemple 2:

Recherche d’un élément dans un tableau

int recherche (float tab[], int n, float elem)

{

int i ; i=0 ; while ((i <n) && (tab[i] !=elem))

i=i+1;

if (i>=n) i=-1; return i;

}

Comme opérations fondamentales, nous ne pouvons prendre que la comparaison de elem avec les éléments du tableau. En effet, tout le reste concerne soit la programmation, soit l’implémentation de liste par un tableau. Deux cas se présentent :

- le nombre d’opérations est égal à n, et i=-1 si elem n’est pas dans le tableau

- le nombre d’opérations est égal à (j+1) si elem est dans le tableau en tab[j] (j variant de 0 à n-1) La complexité dépend de 3 points essentiels :

- le choix des opérations fondamentales

- la taille des données, ici n

- pour une taille données, les différentes données possibles (ici, j varie)

Les branchements (instructions conditionnelles) : difficile de savoir le nombre exact, on va donc majorer :

if (condition) I1 ; else I2 ;

Karine Deschinkel

2007/2008

# (if (condition) I1 ; else I2 ;) <= #(condition) + max(#(I1), #(I2)) avec

# le nombre d’opérations

Les boucles (instructions itératives) : c’est la somme des nombres #(I(i)) d’opérations fondamentales lors de l’exécution de la ième itération de la boucle :

for (i=…) I(i);

#( for (i=…) I(i);) =

de la boucle : for (i=…) I(i); #( for (i=…) I(i);) = #(I(i)) ∑ Les appels

#(I(i))

Les appels de fonctions :

- s’il s’agit des fonctions simples, on applique les mêmes règles de calcul définies précédemment

- s’il s’agit des fonctions récursives, le comptage demande d’une part l’expression d’une équation récurrente, et d’autre part la résolution de cette équation

Exemple :

int fac(int n)

{

if(n>1) return n*fact(n-1) ; else return (1);

}

L’équation récurrente s’exprime comme suit :

T(0) = 0 T(n) = 1 + T(n-1), T(n) = k + T(n-k)

Donc T(n)=

i n i 1
i
n
i
1

1 +

T(0) = n + 0 = n

2.2 Complexité en espace mémoire

Même genre de calcul, mais en terme de cases mémoires (octets) au lieu du nombre d’opérations fondamentales.

2.3 Autre critère d’évaluation possible

Il convient de trouver un compromis entre le temps de calcul et l’occupation mémoire. Pendant la phase d’analyse, il convient de bien prendre en compte le contexte de développement et d’utilisation du programme pour savoir quels aspects privilégier.

3 COMPLEXITÉ EN MOYENNE, AU MEILLEUR ET AU PIRE CAS

Le temps d’exécution d’un algorithme est en général exprimé en fonction des données sur lesquelles il opère. T(n) est le temps d’exécution exprimé en fonction des données de taille n.

Karine Deschinkel

2007/2008

3.1 Une mesure de la taille des données

Il faut donc définir une mesure de taille sur les données. Cette mesure doit refléter la quantité d’information contenue. Par exemple :

- la multiplication d’entiers : taille des nombres = nombre de bits

- la multiplication de matrices : taille des matrices traitées (n * m)

- parcours

de graphes : nombre de sommets/nœuds et

d’arbres/

d’arêtes/arcs

Pour certains algorithmes, le temps d’exécution ne dépend que de la taille des données, on fait dans ce cas une analyse simple. Mais la plupart varie, pour une taille donnée, en fonction des données elles-mêmes. Dans ce cas, il faut faire une analyse au pire, en moyenne et au meilleur cas, mais c’est souvent plus difficile.

3.2 Complexité d’un algorithme A sur des données D n de taille n

Complexité dans le pire des cas Tmax(A(n)) = max { coût A(n) (d), d D n }

Complexité dans le meilleur des cas Tmin(A(n)) = min { coût A(n) (d), d D n }

Complexité en moyenne / dans le cas moyen

d D n } Complexité en moyenne / dans le cas moyen p d coût A

p

d coût A(n) (d)

Tmoy(A(n)) = d D n

p(d) est la probabilité d’avoir la donnée d en entrée de l’algorithme.

On a :

Tmin(A(n))

Tmoy(A(n))

Tmax(A(n))

Cette analyse permet de trouver un comportement « en général » de l’algorithme, plus intéressant, mais elle nécessite aussi de trouver un modèle probabiliste adéquat pour formaliser le temps d’exécution du problème traité et ce n’est pas toujours évident !

3.3 Exemples

a. Multiplication de matrices

void multimat(int A[][], int B[][], int R[][], int n)

{

int i,j,k ; for(i=0; i<n; i++)

for(j=0; j<n; j++)

{

Karine Deschinkel

2007/2008

R[i][j]=0;

for(k=0; k <n; k++) R[i][j] + = A[i][k] * B[k][j];

}

}

Cette fonction a une complexité qui ne dépend que de la taille des matrices :

Tmin(A(n)) = Tmoy(A(n)) = Tmax(A(n)) =

i n i 0
i
n
i
0

1

j

n j 0
n
j
0

1

k

n 1 k 0
n
1
k
0

1

n 3

b. Recherche d’un élément dans un tableau (cf exemple précédent) Pour la recherche d’un élément dans un tableau, il est facile de déterminer les complexités extrêmes :

Tmin(n) = 1 (l’élément est au début du tableau) Tmax(n) = n (l’élément est à la fin du tableau ou n’est pas dans le tableau) Pour calculer Tmoy(n), on doit se donner des probabilités :

- soit q la probabilité que l’élément cherché soit dans le tableau

- on suppose que si l’élément est dans le tableau, toutes les places sont équiprobables On note Dn,k l’ensemble des données où l’élément elem apparaît à la k ième place. On note Dn,0 l’ensemble des données où l’élément elem n’est pas dans le tableau. On a :

p(d Dn,k) = q

coût (d

n

et

p(d

Dn,0) = 1 – q

Dn,0) = n

Dn,k) = k et

coût (d

Ce qui donne en moyenne :

Tmoy(n)

=

k n k 0
k
n
k
0

p(d

Dn,k) coût (d Dn,k)

=

k n n k 1
k
n
n
k
1

(1-q) n + q

k

 

(1-q) n +

n (n+

1

) q

 

=

 
 

2

n

(1-q) n +

n (n+

1

) q

 

=

 
 

2

n

Conclusion :

(n+1 ) , on doit parcourir

2

Si elem est dans le tableau, q=1 et Tmoy(n) =

en moyenne la moitié du tableau avant de le trouver.

Karine Deschinkel

2007/2008

Si elem a une chance sur deux d’être dans le tableau (q=1/2), Tmoy(n) =

3n+1 , il faut parcourir en moyenne ¾ du tableau avant de trouver

4

elem ou non.

4 ORDRES DE GRANDEUR : COMPARAISONS D’ALGORITHMES

Comme nous avons pu constater, les complexités sont exprimées en fonction de la taille des données. Or pour un problème donné, si la taille des données est faible, le choix d’un algorithme par rapport à un autre importe souvent peu. En revanche, lorsque la taille des données devient grande, il convient de bien connaître la croissance de la fonction de complexité.

4.1 Approximations des complexités

Souvent, il suffit d’une approximation simple de la complexité pour savoir si un algorithme est plus efficace qu’un autre :

1. Dans les approximations, il n’est pas utile de tenir compte des constantes additives. Par exemple, il est secondaire de savoir si un algorithme fait n opérations , n+2, ou encore n+10 opérations pour un n grand.

2. De même pour les constantes multiplicatives, mais là, il convient d’être plus prudent au moment de l’approximation. Par exemple, A1 a une complexité (Min, Max, ou Moy) égale à T1(n)=n 2 et A2 de complexité T2(n)=2n est meilleur que A1 pour tous les n>2. De même, si A1 a pour complexité T1(n)=3n 2 et A2 a T2(n)=25n, A2 est meilleur que A1 que si n>8. Toutefois, peu importe les constantes multiplicatives, si T1(n)=k1 n 2 et T2(n)=k2 n, alors l’algorithme A2 est toujours meilleur que A1 à partir d’un certain rang pour n, car la fonction n 2 croît beaucoup plus vite que n : lim n/ n = 0

n

2

On dit alors que l’ordre de grandeur asymptotique de n 2 est strictement plus grand que celui de n.

4.2 Ordre de grandeur asymptotique et notation de ‘Landau’

Supposons que l’on ait à comparer les algorithmes A1 et A2 de complexités respectives T1(n) et T2(n), si l’ordre de grandeur asymptotique de A1 est strictement plus grand que celui de A2, on peut conclure immédiatement que A2 est meilleur que A1 pour n grand. En revanche, si T1(n) et T2(n) ont le même ordre de grandeur asymptotique, il convient de faire une analyse plus fine pour comparer A1 et A2. Pour comparer les ordres de grandeur asymptotique, on a l’habitude d’utiliser la notation « Landau » en mathématiques.

Définitions

Karine Deschinkel

2007/2008

Soient f et g deux fonctions de N dans R+,

1. f = O(g) si et seulement si il existe c dans R+, il existe n0 dans N tel que quelque soit n > n0, f(n) c. g(n). Ainsi f= O(g) veut dire que f est dominée asymptotiquement par g. Par exemple : 2n=O(n 2 ), mais aussi 2n=O(n) aussi ! Il suffit de bien choisir la constante n0. Remarque : lorsqu’une complexité est donnée en termes de O(g), il convient de donner la fonction g la plus « serrée » possible.

2. f = Ω(g) si et seulement si il existe d dans R+, il existe n0 dans N tel que quelque soit n > n0, d.g(n) f(n).

3. f = Θ(g) si et seulement si f=O(g) et g=O(f) ou encore f =O(g) et f=Ω(g). C’est à dire qu’il existe c et d dans R+, il existe n0 dans N tel que quelque soit n > n0,

d.g(n) f(n) c. g(n). On dit que f et g sont de même ordre de grandeur asymptotique. En d’autres termes, O() est une borne asymptotique supérieure, Ω() est la borne asymptotique inférieure et Θ() la borne asymptotique « exacte », cette dernière est beaucoup plus précise que les deux premières.

4.3 Exemple

Complexité du tri par sélection

- Opérations de base = comparaison d’éléments. T(n)=

i =n 2 j =n 1 n 2 1 = i = 0 j =i+
i =n
2
j
=n
1
n
2
1
=
i =
0
j =i+
1
i =
0
=(n
1
)(n
1
)

(n

1

 

n

2

)

(i+ )+ =

1

1

 

n

1

 

i =

0

(n

1

)(n

2

)

2

i =

n

i =

2

0

(n

1

)

n

i =

2

0

i

(n

1

)(

2n

2

n+

2

)

=

2

=

n(n

1

)

2

Le

comparaisons d’éléments.

tri par sélection a une

complexité en Θ(n 2 ) pour le nombre de

- Opérations de base = déplacements ou échanges d’éléments

T(n)=

Le tri par sélection a une complexité en Θ(n) pour le nombre d’échanges d’éléments.

n

2

i=

0

1

=(n

2

)+ = n

1

1