Académique Documents
Professionnel Documents
Culture Documents
Notions de complexité
Dans ce chapitre, nous présentons la notion de complexité, qui décrit le temps et la mémoire
nécessaires pour exécuter un algorithme et caractérise donc son efficacité. Nous étudions ensuite la
complexité de tous les algorithmes présentés.
1.1 Introduction
Un même problème peut être, le plus souvent, résolu par plusieurs algorithmes, c’est pourquoi
il est très important de pouvoir comparer l’efficacité des différents algorithmes pouvant être mis en
œuvre pour résoudre ce problème. Deux critères principaux sont généralement considérés quand on
étudie l’efficacité d’un algorithme ; le premier est le temps de calcul qu’on appelle aussi parfois coût
de l’algorithme, le second est l’espace mémoire nécessaire à l’exécution de l’algorithme. Si l’espace
mémoire reste un critère important de l’évaluation de l’efficacité d’un algorithme, en pratique, dans
la majorité des problèmes à résoudre, le temps de calcul devient un paramètre principal mesurant
l’efficacité d’un algorithme. Par exemple, pour un logiciel interactif, un temps de réponse court est
un élément essentiel du confort de l’utilisateur. De même, certains programmes industriels doivent
être utilisés un grand nombre de fois dans un délai très court.// Dans ce cours, nous nous inté-
resserons à la complexité en temps d’un algorithme que, par abus de langage, nous appellerons
simplement « complexité d’un algorithme ».
Exemple :
1 def diviseurs ( n ) :
2 for i in range (1 , n +1) :
3 if n % i == 0 :
4 print ( i )
1
CPGE :Mohammedia CHAPITRE 1. NOTIONS DE COMPLEXITÉ
6 if n // i != i :
7 print ( n // i )
√ √ √
Cet algorithme réalise un calcul √ de racine carrée, b nc calculs de reste, entre b nc et 2b nc
comparaisons et entre 1 et 2b nc affichages.
En général, on cherche à déterminer comment le temps d’exécution d’un algorithme varie en fonction
d’un paramètre qu’on appelle la taille du problème. Le temps de recherche des diviseurs d’un entier n
dépend den, qu’on pourra donc naturellement prendre comme√taille du problème. Comme on l’a vu,
selon l’algorithme, ce temps peut être proportionnel à n ou à n. De même, quand on s’interrogera
sur l’efficacité d’un algorithme manipulant des tableaux, on cherchera à comprendre comment le
temps d’exécution de cet algorithme varie en fonction du nombre d’éléments de ce tableau. Une
autre possibilité pour la taille du problème est de considérer la taille de la représentation des données
passées à ce programme. Par exemple, pour un programme traitant un texte, on prend pour taille
du problème le nombre de caractères de ce texte.
L’évaluation du temps mis par un algorithme pour s’exécuter est un domaine de recherche à part
entière, car elle se révèle parfois très difficile. Néanmoins, dans de nombreux cas, cette évaluation
peut se faire en appliquant quelques règles simples.
les différentes opérations élémentaires considérées ne demandent pas toutes exactement le même
temps de calcul et cachent donc un facteur multiplicatif, borné mais très compliqué à déterminer
précisément. D’autre part, le même programme peut être exécuté sur deux machines différentes,
l’une étant par exemple deux fois plus rapide que l’autre. Cela ne change évidemment rien à l’ef-
ficacité intrinsèque de l’algorithme et ce qui nous intéresse réellement n’est pas le temps précis
d’exécution d’un programme, mais l’ordre de grandeur de ce temps en fonction de la taille des
2TSI 2 A.HAOUDIGUI
CPGE :Mohammedia CHAPITRE 1. NOTIONS DE COMPLEXITÉ
données.
Une dernière notion à considérer est celle du terme dominant dans le temps d’exécution d’un
algorithme. Par exemple, si on a déterminé que ce temps était proportionnel àn2 + 3n, dès que la
taille n des données devient un peu importante, il est connu que le terme 3n augmente beaucoup
moins vite que n2 : on dit qu’il est négligeable devant ce dernier. Pour décrire l’efficacité d’un
algorithme, seul le terme qui croît le plus vite a donc un intérêt. Par exemple, ici pour n ≥ 3, on
a n2 + 3n ≤ 2n2 ; la quantité n2 + 3n est donc bornée, à partir d’un certain rang, par le produit
de n2 et d’une constante. On dit alors que la quantité de n2 + 3n est « un grand O de n2 » et on
écrira n2 + 3n = O(n2 ).
1.4 La relation O
La notation f (x) = O(g(x)) signifie qu’il existe c > 0 tel que f (x) ≤ c.g(x) dès que x est
suffisamment grand. Noter qu’elle ne s’applique qu’à des fonctions positives à partir d’une certaine
valeur n0 . On a :
— Réflexivité : f (x) = O(f (x))
— transitivité : si f (x) = O(g(x)) et g(x) = O(h(x)), alors f (x) = O(h(x))
— linéarité : si f 1(x) = O(f 2(x)) et g1(x) = O(g2(x)), alors f1 (x) + a.g1(x) = O(f 2(x) +
a.g2(x)). f (x) = O(a.f (x)) pour tout réel a > 0 ;
De manière générale, on dira qu’un algorithme a une complexité en O(f(n)) si son coût est, à
partir d’un certain rang, inférieur au produit de f(n) par une constante .
2TSI 3 A.HAOUDIGUI
CPGE:Mohammedia CHAPITRE 1. NOTIONS DE COMPLEXITÉ
2TSI 4 A.HAOUDIGUI
CPGE :Mohammedia CHAPITRE 1. NOTIONS DE COMPLEXITÉ
6 return 0.5 * ( x + 3. / x )
C(0) = 0
C(n) = C(n − 1) + 3
En effet dans le cas n = 0, on ne fait aucune opération arithmétique. Et dans le cas n > 0, on fait
d’une part un appel récursif sur la valeur n − 1, d’où C(n − 1) opérations, puis trois opérations
arithmétiques (une multiplication, une addition et une division). Il s’agit d’une suite arithmétique
de raison 3, dont le terme général est :
C(n) = 3n
Le nombre d’opérations arithmétiques effectuées par la fonction u est donc proportionnel à n. Si en
revanche on avait écrit la fonction u plus naïvement, avec deux appels récursifs u(n−1), c’est-à-dire :
1 def u ( n ):
2 if n == 0:
3 return 2.
4 else :
5 return 0.5 * ( u (n -1) + 3. / u (n -1))
C(0) = 0
C(n) = 3(2n − 1)
Une autre manière d’évaluer le coût d’une fonction récursive est de calculer le nombre d’appels,
puis d’évaluer le coût de chaque appel. Si on note A(n) le nombre d’appels récursifs dans les deux
exemples précédents, on a :
A(0) = 0
A(n) = A(n − 1) + 1
dans le premier cas, et :
A(0) = 0
A(n) = A(n − 1) + A(n − 1) + 1
dans le second cas.
Le terme général est donc A(n) = n dans le premier cas et A(n) = 2n − 1 dans le second. Puisqu’on
n’a ici aucune opération arithmétique dans le cas de base n = 0 et exactement trois opérations
arithmétiques dans le cas récursif, on retrouve immédiatement la valeur de C(n) calculée précé-
demment. D’une manière générale, la valeur de C(n) ne se déduit pas toujours aussi facilement de
la valeur de A(n). En effet, il peut y avoir des opérations dans le cas de base et/ou un nombre
d’opérations arithmétiques variant selon la valeur de n dans le cas récursif.
2TSI 5 A.HAOUDIGUI
CPGE :Mohammedia CHAPITRE 1. NOTIONS DE COMPLEXITÉ
Chaque appel récursif alloue de la mémoire pour les paramètres effectifs et les variables locales
de cet appel. L’occupation mémoire d’un calcul récursif admet donc pour majorant le produit du
nombre d’appels récursifs par la quantité de mémoire allouée par chaque appel. Dans les deux
exemples précédents, on a calculé explicitement le nombre d’appels A(n). L’occupation mémoire
est donc 2n dans le premier cas (il y a deux cases mémoire, une pour n et une autre pour x) et
2n −1 dans le second cas (il y a une case mémoire, pour n). Cependant, dans le second cas, les 2n −1
cases mémoire ne seront pas utilisées simultanément. En effet, celles allouées pour le premier appel
à u(n − 1) peuvent être réutilisées pour le second (et en pratique elles le sont). Pour une analyse
plus fine de l’occupation mémoire, il convient donc de calculer le nombre d’appels imbriqués.
2TSI 6 A.HAOUDIGUI