Algorithmique avancé et
calcul de complexité
Dr. Célia HIRECHE
Maître de Conférences B - USDB
Chargée de recherche – LRIA – USTHB
hireche_celia@[Link] (seulement pour les délégués)
Chapitre I :
La complexité de calcul
1. INTRODUCTION
2. EXEMPLES INTRODUCTIFS
3. ALGORITHME ET COMPLEXITÉ
4. COMPLEXITÉ THÉORIQUE : COMPLEXITÉ TEMPORELLE VS COMPLEXITÉ SPATIALE
5. COMPLEXITÉ TEMPORELLE.
1. Définition et notion de landau
2. Règles de calcul
3. Exemples
4. Classes de complexité
INTRODUCTION
Un algorithme est suite finie d’opérations élémentaires, organisée selon des règles
précises constituant un schéma de calcul ou de résolution d’un problème.
Double problématique de l’algorithmique
1. Trouver une méthode de résolution (exacte ou approchée) du problème.
2. Trouver une méthode efficace.
Pour un même problème, il peut exister plusieurs algorithmes.
Par exemple, recherche une valeur dans un ensemble d’entier; la recherche séquentielle est
moins efficace que la recherche dichotomique lorsque les éléments sont triés.
Efficacité : - Effectiveness : Evaluation de la solution en elle-même.
- Efficiency : Evaluation des ressources nécessaires pour atteindre la solution.
Savoir résoudre un problème est une chose, le résoudre efficacement en est une autre.
Introduction
La conception d’un algorithme pour un problème donné ne constitue pas une tâche ardue,
le plus souvent.
Cependant, pour un même problème, il peut exister plusieurs algorithmes.
L’étude de la complexité d’un algorithme permet de déterminer l’algorithme qui nécessite
le moins de code et un temps d’exécution le plus court possible.
Exemple
Concevoir un algorithme qui affiche le calcul des fréquences de nombres entiers positifs dans un
tableau de taille n.
Principe de l’algorithme 1
Le principe de l’algorithme 1 est de considérer un par un les éléments du tableau.
Pour chaque élément, on vérifie la partie gauche du tableau, si l’élément en question
existe dans cette partie cela signifie qu’il a déjà été traité. Sinon (dans le cas où
l’élément n’existe pas sur la gauche), on parcourt tous les éléments sur la droite pour
calculer la fréquence de ce dernier.
Algorithme 1
Pour i=1 à n
j=1
Trouve= faux
Tant que (j<i) et (trouve = faux)
Si T[i] = T[j] alors Trouve = vrai
sinon j = j+1
Si ( trouve = faux) alors fréquence =1
Pour j =i+1 à n
si T[i] = T[j] alors fréquence = fréquence +1
Ecrire (« Fréquence de T[i] = fréquence »)
Algorithme 1
Pour i=1 à n
j=1
Trouve= faux
Tant que (j<i) et (trouve = faux)
Si T[i] = T[j] alors Trouve = vrai
sinon j = j+1
Si ( trouve = faux) alors fréquence =1
Pour j =i+1 à n
si T[i] = T[j] alors fréquence = fréquence +1
Ecrire (« Fréquence de T[i] = fréquence »)
Analyse et calcul du nombre d’instructions effectué au pire cas
Pire cas? C’est le cas le plus défavorable où l’algorithme exécute un nombre maximal
d’instructions.
Pour cet algorithme, c’est le cas où tous les éléments sont distincts.
L’algorithme 1 possède 3 boucles, deux boucles internes séquentielles imbriquées dans une
boucle externe. Il procède au parcourt du tableau n fois et pour chacun des éléments du
tableau, effectue dans le pire cas un parcourt de l’ensemble du tableau (n-1 fois).
Le nombre total d’exécutions est égal à n* (n-1) = n²-n. La complexité sera alors de l’ordre
de n² et sera notée O(n²).
Exemple
Concevoir un algorithme qui affiche le calcul des fréquences de nombres entiers positifs dans un
tableau de taille n.
Algorithme 2
Pour i=1 à n
Si T[i] =! -1
fréquence = 1
Pour j = i+1 à n
Si T[j] = T[i] alors fréquence = fréquence +1
T[j] = -1
Ecrire (« Fréquence de T[i] = fréquence »)
Principe et analyse de l’algorithme 2
L’algorithme 2 est une version améliorée de l’algorithme 1, à chaque fois qu’un élément est
traité, sa valeur est mise à -1. Dans ce cas, il est inutile de faire la recherche de l’élément
courant à gauche du tableau et par conséquent la première boucle interne est supprimée. La
seconde boucle (interne) est quant à elle conservée et est exécutée n-i fois pour chaque
itération de la boucle externe.
𝑖=𝑛 𝑛 𝑛−1 𝑛2 −𝑛
Le nombre total d’exécutions est alors de 𝑖=1 (𝑛 − 𝑖) = = . La complexité est alors
2 2
de l’ordre de n² et sera notée O(n²).
Remarque
En comparant les deux algorithmes 1 et 2, on constate que la complexité de
l’algorithme 2 est réduite de moitié par rapport l’algorithme 1 mais l’ordre est le
même.
Exemple
Algorithme 3
Pour i=1 à max (max représente la plus grande valeur positive contenu dans le tableau)
fréquence [i] = 0
Pour i =1 à n
fréquence [T[i]] = fréquence [T[i]]+1
Pour i = 1 à max
Ecrire (« Fréquence de T[i] = fréquence »)
Principe et analyse de l’algorithme 3
Dans ce algorithme, un second tableau de fréquence est utilisé. Ce tableau contient max (max>n)
éléments correspondant à la plus grande valeur contenu dans le tableau des nombres entiers ( de 0 à
max). Ce tableau est d’abord initialisé à 0, pour se faire un parcourt du tableau (fréquence est
nécessaire) donc max.
Le tableau contenant les nombre entiers est alors parcouru et pour chaque nombre, sa fréquence est
incrémentée. Le tableau est donc parcouru une seule fois donc n.
Une fois le tableau des fréquences rempli, ce dernier est parcouru pour afficher les fréquence. Ce
parcours vaut max fois.
Le nombre total d’instruction est alors 2max + n et comme max>n, la complexité est de l’ordre de
max et sera notée O(max).
Remarque
En comparant les trois algorithmes 1, 2 et 3, on constate que la complexité de l’algorithme 2
est réduite de moitié par rapport l’algorithme 1 mais l’ordre est le même.
L’algorithme 3 permet de réduire la complexité de n² à max. Cependant, dans le cas où max est
très grand par rapport à n, l’espace mémoire requis pour le tableau des fréquences sera très
important et dans ce cas on constate que la complexité temporelle (temps d’exécution) est
réduite au détriment de l’espace mémoire.
Algorithme et complexité
Lorsque l’on considère la résolution d’un problème donné, il peut être intéressant de
trouver plusieurs algorithmes pour le résoudre, afin d’en exploiter le meilleur de ces
algorithmes.
Seulement, comment déterminer quel est l’algorithme le plus approprié? Le meilleur
algorithme? Le plus efficace?
Deux approches :
L’approche expérimentale
Dans ce cas, plusieurs algorithmes sont implémentés et exécutés sur différents jeu
de données afin d’en déterminer le plus efficient (celui dont le temps d’exécution est le
plus faible).
L’approche théorique
Cette approche consiste à déterminer mathématiquement la quantité de ressources
(que ce soit en temps d’exécution ou en espace mémoire) nécessaire pour un
algorithme donné en fonction de la taille du jeu de données considéré.
Complexité théorique
Différents paramètres déterminent le temps d’exécution d’un algorithme. Nous citerons;
La machine utilisée ( vitesse du processeur, mémoire vive, …)
Le langage de programmation utilisé (compilateur)
La difficulté de l’algorithme et les astuces du programmeur.
L’approche théorique de calcul de complexité a pour principal avantage de ne pas prendre
en considération tous ces paramètres mais permet d’évaluer l’efficacité d’un algorithme en
fonction de la taille du jeu de données traité.
Complexité Temporelle vs Complexité Spatiale
Complexité temporelle
La complexité temporelle désigne le temps d’exécution d’un algorithme en fonction des
données du problème à résoudre et est exprimé en unité de temps (seconde en général).
Complexité spatiale
La complexité spatiale désigne quant à elle la quantité de ressources en terme d’espace
mémoire utilisé par cet algorithme lors de son exécution en fonction de la taille du
problème traité.
L’objet de ce chapitre est le calcul de la complexité temporelle.
Complexité temporelle
(Définition)
La complexité temporelle théorique d’un algorithme revient à calculer le nombre
d’instructions (opérations) élémentaires effectuées par l’algorithme dans le pire des
cas.
La complexité d’un algorithme est la mesure du nombre d’opérations fondamentales
qu’il effectue sur un jeu de données. La complexité est exprimée comme une fonction
de la taille du jeu de données.
Nous notons 𝐷𝑛 l’ensemble des données de taille 𝑛 et 𝑇(𝑑) le coût de l’algorithme
sur la donnée 𝑑.
Complexité temporelle
(Définition)
Complexité au meilleur :𝑇min 𝑛 = min 𝐶(𝑑)
𝑑 ∈𝐷
C’est le plus petit nombre d’opérations qu’aura à exécuter l’algorithme sur un jeu de
données de taille n. C’est une borne inférieure de la complexité de l’algorithme sur un
jeu de données de taille n.
Complexité au pire : 𝑇max(𝑛) = 𝑚𝑎𝑥𝑑∈𝐷𝑛 𝐶(𝑑)
C’est le plus grand nombre d’opérations qu’aura à exécuter l’algorithme sur un jeu
de données de taille n. Il s’agit d’un maximum, et l’algorithme finira donc toujours
avant d’avoir effectué 𝑇max(𝑛) . Cette complexité peut ne pas refléter le comportement «
usuel » de l’algorithme, le pire cas pouvant ne se produire que très rarement, mais il
n’est pas rare que le cas moyen soit aussi mauvais que le pire cas.
Complexité temporelle
(Définition)
𝑑∈𝐷𝑛 𝐶(𝑑)
Complexité en moyenne : 𝑇𝑚𝑜𝑦(𝑛) =
|𝐷𝑛 |
C’est la moyenne des complexités de l’algorithme sur des jeux de données de
taille n. Reflète le comportement « général » de l’algorithme si les cas extrêmes sont
rares ou si la complexité varie peu en fonction des données.
La complexité en pratique sur un jeu de données particulier peut être nettement plus
importante que la complexité en moyenne, dans ce cas la complexité en moyenne ne
donnera pas une bonne indication du comportement de l’algorithme.
En pratique, nous ne nous intéresserons qu’à la complexité au pire.
1ere loi de Murphy « Anything that can go wrong, will go wrong »
En résumé
La complexité temporelle d’un algorithme est la mesure du nombre d’opérations
fondamentales (affectation, opération arithmétique, …) qu’il effectue sur un jeu de
données de taille n.
Le pire des cas = Le cas le plus défavorable où l’algorithme effectue le plus grand
nombre d’opérations.
Exemple : La recherche séquentielle d’une valeur non existante. L’algorithme devra
obligatoirement parcourir tout le tableau.
Complexité temporelle
(Définition et notion de Landau)
Evidemment, on ne peut pas exprimer théoriquement la complexité temporelle en unité de
temps.
Une notation asymptotique est utilisée pour représenter cette dernière communément
appelée notation de Landau et représentée par O.
Cette notation représente l’ordre de la complexité qui est la limite supérieur de la formule
obtenue après calcul du nombre d’instructions effectuées.
Si, par exemple, un algorithme donné pour un jeu de données de taille n s’exécute en un
temps égal à 𝑐 ∗ 𝑛3 On dira que la complexité de cet algorithme est de l’ordre de 𝑛3 que l’on
notera 𝑂(𝑛3 ).
Exemple: Pour un algorithme dont le nombre d’instructions est 𝑛2 + 3𝑛 + 12 l’ordre et la
complexité sera de 𝑂 𝑛2 .
Complexité temporelle
(Définition et notion de Landau)
Règles de la notation de Landau
Complexité (Nombre instructions)
Ordre Notation de Landau
exemple
Constante Constante O(1)
Addition : 5n+12m n+m O(max(n,m))
Constante multiplicative : 12n n O(n)
Multiplication : n*m N*m O(n*m)
Complexité temporelle
(Exemple1)
Que fait cet algorithme? (Supposons que les
Algorithme 1; deux tableaux ne contiennent pas de
Const max = 1000; valeurs répétitives).
Var nb, i, n : entier;
T1, T2 : tableau[max] de entier; L’algorithme lis deux tableaux d’entiers et
Debut calcul le nombre de valeurs identiques dans
nb=0; les deux tableaux.
Pour i=1 à n
Lire (T1[i])
Pour i=1 à n Quel est le pire cas?
Lire (T2[i])
Pour i=1 à n Le pire cas est celui où les deux tableaux sont
si (T1[i] = T2[i]) alors nb = nb+1 identiques (contiennent les même valeurs à
Fin. chaque itération. Car cela engendrerait une
instruction supplémentaire (incrémentation) à
chaque itération.
Règle de calcul : Traitement conditionnel
En règle générale, le temps d’exécution d’une instruction conditionnel est égal à celui de
l’exécution de la condition + celle du bloc d’instructions du cas le plus défavorable.
Si <Condition>
alors <Bloc actions 1>
sinon <Bloc actions 2>
Complexité = Complexité (Condition ) + Max (Complexité(Bloc 1) ; Complexité (Bloc 2)) selon le pire cas.
Règle de calcul : Traitement répétitif (boucles)
En règle générale, le temps d’exécution d’une boucle est égal à celui du corps de la
boucle multiplier par le nombre d’itération de celle-ci.
Pour i = Id à If avec pas= P faire
<Bloc actions>
Complexité = Nombre itérations * Complexité (Bloc actions).
Complexité Boucle Pour = ((If – Id)/P +1) * Complexité(Bloc actions)
Complexité temporelle
(Exemple1)
Algorithme 1;
Const max = 1000; Instruction Nombre de répétition
Var nb, i, n : entier; (complexité)
T1, T2 : tableau[max] de entier;
Inst 1 : affectation 1
Debut
nb=0; Inst 1 Inst 2 : lecture (boucle) ((n-1)/1 +1)*1 =n
Pour i=1 à n
Lire (T1[i]) Inst 2 Inst 3 : lecture (boucle) n
Pour i=1 à n
Lire (T2[i]) Inst 3 Inst 4 : comparaison (boucle) n
Pour i=1 à n
si (T1[i] = T2[i]) Inst 4 Inst 5 : affectation + addition n *2 = 2n
alors nb = nb+1 Inst 5 (boucle) pire cas
Fin.
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠 = 1 + 𝑛 + 𝑛 + 𝑛 + 2𝑛 = 5𝑛 + 1 ⇒ 𝐶𝑜𝑚𝑝𝑙𝑒𝑥𝑖𝑡é = 𝑂(𝑛) Complexité linéaire
Complexité temporelle
(Exemple2)
Que fait cet algorithme? (Supposons que les
Algorithme 2; deux tableaux ne contiennent pas de
Const max = 1000; valeurs répétitives).
Var nb, i, n : entier;
T1, T2 : tableau[max] de entier; L’algorithme lis deux tableaux d’entiers et
Debut calcul le nombre de valeurs identiques dans
nb=0; les deux tableaux.
Pour i=1 à n
Lire (T1[i])
Pour i=1 à n Quel est le pire cas?
Lire (T2[i])
Pour i=1 à n Le pire cas est celui où les deux tableaux n’ont
j=1 aucune valeur commune (aucune valeur de T1
Tant que (j<= n et T1[i] =! T2[j]) n’existe dans T2 et inversement). Car cela
j= j+1 engendrerait le parcours de tout le second
Si j<=n alors nb = nb+1 tableau à chaque itération,
Fin.
Règle de calcul : Boucles imbriquées
Une boucle imbriquée n’est autre qu’une simple boucle où une instruction est
remplacée par une autre boucle. Le calcul de la complexité revient à calculer le nombre
d’instructions de la boucle interne multiplier par le nombre d’itérations de celle-ci
multiplier par le nombre d’itérations de la boucle externe.
Pour i = Id à If avec pas= P faire Complexité = Nombre itérations * Complexité (Bloc actions)
<Bloc actions>
Bloc actions :
Pour j= Id à If avec pas= P Complexité = Nombre itérations Boucle externe * Nombre
<instructions> itérations boucle interne * Complexité (instruction)
Complexité temporelle
(Exemple2)
Algorithme 2; Instruction Nombre de répétition
Const max = 1000; (complexité)
Var nb, i, n : entier; Inst 1 : affectation 1
T1, T2 : tableau[max] de entier;
Inst 2 : lecture (boucle) n
Debut
nb=0; Inst 1
Pour i=1 à n Inst 3 : lecture (boucle) n
Lire (T1[i]) Inst 2
Pour i=1 à n Inst 4 : affectation (boucle) n
Lire (T2[i]) Inst 3
Inst 5 : affectation + addition n*n*2 = 2n²
Pour i=1 à n
(boucle imbriqué)
j=1 Inst 4
Tant que (j<= n et T1[i] =! T2[j])
j= j+1 Inst 5 Inst 6 : Comparaison n
Si j<=n Inst 6 (boucle)
alors nb = nb+1 Inst 7
Fin. Inst 7 : affectation + addition 0
Pas inclus dans le pire cas
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠 = 1 + 𝑛 + 𝑛 + 𝑛 + 2𝑛2 + 𝑛 = 2𝑛2 + 4𝑛 + 1 ⇒ 𝐶𝑜𝑚𝑝𝑙𝑒𝑥𝑖𝑡é = 𝑂(𝑛²)
Complexité Quadratique
Complexité temporelle
(Exemple3)
Algorithme Recherche_dichotomique
Const max = 1000;
Var Binf, Bsup, m, x, n : entier; Algorithme de recherche dichotomique
T: tableau[max] de entier; (tableau trié tri croissant).
Debut
Binf = 0;
Quel est le pire cas?
Bsup = n-1;
Le pire cas est celui où la valeur recherchée
Tant que (Binf <= Bsup et trouve = faux)
n’existe pas dans le tableau.
m= (Binf+Bsup) DIV2;
Si T[m]= x
alors trouve= vrai;
sinon Si (x < T[m])
alors Bsup= m-1;
sinon Binf = m+1;
Fin.
Complexité temporelle
(Exemple3)
Instruction Nombre de répétition
Algorithme Recherche_dichotomique (complexité)
Const max = 1000;
Inst 1 : affectation 1
Var Binf, Bsup, m, x, n : entier;
T: tableau[max] de entier; Inst 2 : affectation + 2
Debut soustraction
Binf = 0; Inst 1 Inst 3 : Afectation + addition Nbr_iter * 3
Bsup = n-1; Inst 2 + div (Boucle)
Tant que (Binf <= Bsup et trouve = faux)
Inst 4 : comparaison (boucle) Nbr_iter *1
m= (Binf+Bsup) DIV 2; Inst 3
Si T[m]= x Inst 4 Inst 5 : affectation (boucle) 0
alors trouve= vrai; Inst 5 Non inclus dans pire cas
sinon Si (x < T[m]) Inst 6
alors Bsup= m-1; Inst 7 Inst 6 : Comparaison Nbr_iter *1
sinon Binf = m+1; Inst 8 (boucle)
Fin.
Inst 7 et Inst 8: affecttion + Nbr_iter *2
soustraction (boucle)
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠 = 1 + 2 + 7 ∗ 𝑁𝑏𝑟_𝑖𝑡𝑒𝑟
Nbr_iter????
Complexité temporelle
(Exemple3)
Algorithme Recherche_dichotomique
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠 = 1 + 2 + 7 ∗ 𝑁𝑏𝑟_𝑖𝑡𝑒𝑟
Const max = 1000;
Var Binf, Bsup, m, x, n : entier;
T: tableau[max] de entier; Nbr_iter????
Debut Le pire cas est celui où la valeur recherchée
Binf = 0; Inst 1 n’existe pas dans le tableau, dans ce cas le tableau
Bsup = n-1; Inst 2 est divisé sur deux à chaque itération jusqu’à ce
Tant que (Binf <= Bsup et trouve = faux) que Bsup<Binf.
m= (Binf+Bsup) DIV 2; Inst 3
Si T[m]= x Inst 4 Le tableau est de taille:
𝒏
alors trouve= vrai; Inst 5 n pour la première itération. ⇒ 𝟐𝟎
sinon Si (x < T[m]) Inst 6
alors Bsup= m-1; Inst 7 n/2 pour la 2eme itération. ⇒ 𝟐𝟏
𝒏
sinon Binf = m+1; Inst 8 𝒏
Fin. n/4 pour la 3eme itération. ⇒ 𝟐𝟐
𝒏
n/8 pour la 4eme itération. ⇒ 𝟐𝟑
…
𝒏
1 pour la dernière itération. ⇒ 𝟐𝑵𝒃𝒓_𝒊𝒕𝒆𝒓
Complexité temporelle
(Exemple3)
𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠 = 1 + 2 + 7 ∗ 𝑁𝑏𝑟_𝑖𝑡𝑒𝑟
Nbr_iter????
Le pire cas est celui où la valeur recherchée 𝒏
n’existe pas dans le tableau, dans ce cas le tableau = 𝟏 ⇒ 𝟐𝑵𝒃𝒓_𝒊𝒕𝒆𝒓 = 𝒏
𝟐𝑵𝒃𝒓_𝒊𝒕𝒆𝒓
est divisé sur deux à chaque itération jusqu’à ce
que Bsup<Binf. 𝟐𝑵𝒃𝒓_𝒊𝒕𝒆𝒓 = 𝒏 ⇒ 𝑳𝒐𝒈𝟐 𝟐𝑵𝒃𝒓𝒊𝒕𝒆𝒓 = 𝑳𝒐𝒈𝟐 𝒏
⇒ 𝑵𝒃𝒓_𝒊𝒕𝒆𝒓 = 𝑳𝒐𝒈𝟐 (𝒏)
Le tableau est de taille:
𝒏
n pour la première itération. ⇒ 𝟐𝟎
𝒏
n/2 pour la 2eme itération. ⇒ 𝟐𝟏
𝒏 𝑖𝑛𝑠𝑡𝑟𝑢𝑐𝑡𝑖𝑜𝑛𝑠 = 1 + 2 + 7 ∗ 𝑁𝑏𝑟𝑖𝑡𝑒𝑟 = 7𝐿𝑜𝑔2 𝑛 + 3 ⇒ 𝑂(𝐿𝑜𝑔2 𝑛 )
n/4 pour la 3eme itération. ⇒ 𝟐𝟐
𝒏
n/8 pour la 4eme itération. ⇒ 𝟐𝟑
… Complexité Logarithmique
𝒏
1 pour la dernière itération. ⇒ 𝟐𝑵𝒃𝒓_𝒊𝒕𝒆𝒓
Classes de complexité
Classe de complexité Exemple d’algorithme
Logarithmique 𝑂(log m 𝑛 ) Diviser successivement un ensemble de
données en m parties.
Recherche dichotomique (m=2).
Linéaire 𝑂(𝑛) Parcours séquentiel d’un ensemble de
données de taille n.
Recherche séquentiel dans un tableau.
Quasi logarithmique 𝑂(𝑛 𝑙𝑜𝑛𝑔 𝑛 ) Décomposition d’un problème en sous
problèmes à traiter indépendamment et
combiner les solutions partielles pour calculer
la solution générale.
Tri fusion.
Quadratique 𝑂(𝑛2 ) Algorithme avec deux boucles imbriquées.
Parcours d’un tableau bidimensionnel.
Polynomiale 𝑂(𝑛𝑝 ) Algorithme avec p boucles imbriquées.
Exponentielle 𝑂(𝑎𝑛 ) Générer tous les sous-ensembles possibles
d'un ensemble de données
Classes de complexité
END OF CHAPTER