Vous êtes sur la page 1sur 111

Complexité Algorithmique

M. Eric VEKOUT, M. Ing., PMP, Prince2, PSM2


PRESENTATION DU COURS
• 1) Définition de la complexité algorithmique
• 2) Paramètre de complexité
• 3) Complexité en temps et en espace
• 4) Calcul de complexité
• 5) Complexité au meilleur et au pire
• 6) Exemples de complexité : recherche séquentielle, recherche dichotomique, tri, récursivité
• 7) Classes de complexité
• 8) Complexité asymptotique
• 9) Notation de Landau et équivalence asymptotique
• 10) Complexité et puissance des machines
DÉFINITION DE LA COMPLEXITÉ ALGORITHMIQUE
• La complexité algorithmique est un concept qui permet de mesurer la
performance d’un algorithme, c’est-à-dire la quantité de ressources (temps,
espace, etc.) nécessaire pour résoudre un problème donné. La complexité
algorithmique est aussi appelée coût ou efficacité d’un algorithme.
• Il existe plusieurs types de complexité, selon le critère choisi pour évaluer
l’efficacité d’un algorithme. Par exemple, la complexité en temps mesure le
nombre d’opérations élémentaires effectuées par l’algorithme, tandis que la
complexité en espace mesure la quantité de mémoire utilisée par l’algorithme.
D’autres types de complexité peuvent être considérés, comme la complexité en
communication, en énergie, en parallélisme, etc.
DÉFINITION DE LA COMPLEXITÉ ALGORITHMIQUE
• La complexité algorithmique ne dépend pas du langage de
programmation, du compilateur, ou du processeur utilisé pour
implémenter l’algorithme, mais seulement de la nature du problème
et de la logique de l’algorithme. Ainsi, la complexité algorithmique
permet de comparer les algorithmes entre eux, et de choisir le plus
adapté à une situation donnée.
DÉFINITION DE LA COMPLEXITÉ ALGORITHMIQUE
• Pour illustrer le concept de complexité algorithmique, prenons l’exemple
du problème de la recherche d’un élément dans un tableau. Il existe
plusieurs algorithmes qui permettent de résoudre ce problème, mais ils
n’ont pas la même complexité. Par exemple, l’algorithme de la recherche
séquentielle consiste à parcourir le tableau du début à la fin, et à comparer
chaque élément avec celui recherché.
• Si on trouve l’élément, on arrête le parcours et on renvoie sa position.
Sinon, on renvoie -1. Cet algorithme a une complexité en temps de O(n),
où n est la taille du tableau, car il faut potentiellement examiner tous les
éléments du tableau. Il a aussi une complexité en espace de O(1), car il
n’utilise pas de mémoire supplémentaire.
PARAMETRE DE COMPLEXITÉ
• Le paramètre de complexité d’un algorithme est la variable qui
représente la taille du problème à résoudre. Par exemple, si le
problème est de trier un tableau de N éléments, le paramètre de
complexité est N. Si le problème est de trouver le plus court chemin
entre deux sommets d’un graphe de N sommets et M arêtes, le
paramètre de complexité peut être N, M, ou N+M, selon la façon dont
on modélise le problème.
PARAMETRE DE COMPLEXITÉ
• Le choix du paramètre de complexité est important, car il influe sur la
valeur de la complexité en temps et en espace de l’algorithme. Par
exemple, si on choisit N comme paramètre de complexité pour le
problème du plus court chemin, on peut dire qu’un algorithme qui
utilise un tableau de N cases a une complexité en espace de N, mais si
on choisit M comme paramètre de complexité, on doit dire que
l’algorithme a une complexité en espace de N/M, ce qui est différent.
PARAMETRE DE COMPLEXITÉ
• Le paramètre de complexité peut être aussi une fonction de plusieurs
variables, si le problème dépend de plusieurs paramètres.
• Par exemple, si le problème est de multiplier deux matrices carrées
de taille N, le paramètre de complexité peut être N, ou N2, ou N3,
selon la façon dont on mesure la taille des matrices.
• Si le problème est de trouver le plus grand commun diviseur de deux
nombres A et B, le paramètre de complexité peut être A, B, ou A+B,
selon la façon dont on mesure la taille des nombres.
PARAMETRE DE COMPLEXITÉ
• Le paramètre de complexité peut être aussi un paramètre
symbolique, si le problème dépend d’un paramètre qui n’est pas
directement lié à la taille des données.
• Par exemple, si le problème est de trouver le nombre de façons de
placer K reines sur un échiquier de taille N x N, sans qu’elles se
menacent mutuellement, le paramètre de complexité peut être K, ou
N, ou K+N, selon la façon dont on modélise le problème.
PARAMETRE DE COMPLEXITÉ
• Le choix du paramètre de complexité doit être pertinent, c’est-à-dire
qu’il doit refléter la difficulté du problème et la performance de
l’algorithme. Par exemple, si on choisit N comme paramètre de
complexité pour le problème du tri d’un tableau, on doit s’assurer que
N est suffisamment grand pour que le tri ne soit pas trivial. Si on
choisit N comme paramètre de complexité pour le problème du plus
court chemin, on doit s’assurer que N est suffisamment petit pour
que le graphe ne soit pas trop dense.
PARAMETRE DE COMPLEXITÉ
• Pour illustrer le concept de paramètre de complexité, prenons l’exemple du
problème de la factorisation d’un nombre entier. Il existe plusieurs
algorithmes qui permettent de résoudre ce problème, mais ils n’ont pas la
même complexité.
• Par exemple, l’algorithme de la factorisation naïve consiste à tester tous les
diviseurs potentiels de 2 à la racine carrée du nombre. Si on trouve un
diviseur, on le garde et on divise le nombre par ce diviseur, et on
recommence le processus. Cet algorithme a une complexité en temps de
O( 𝑵​), où N est le nombre à factoriser, car il faut potentiellement tester
tous les nombres jusqu’à la racine carrée de N. Il a aussi une complexité en
espace de O(1), car il n’utilise pas de mémoire supplémentaire.
PARAMETRE DE COMPLEXITÉ
• Un autre algorithme qui permet de résoudre le même problème est la
factorisation par la méthode de Fermat. Cet algorithme suppose que le
nombre à factoriser est impair, et utilise le fait que tout nombre impair
peut s’écrire comme la différence de deux carrés.
• Il consiste à chercher deux nombres A et B tels que N = A2 - B2, ce qui
implique que N = (A+B)(A-B). Si on trouve un tel couple, on a trouvé une
𝟒
factorisation de N. Cet algorithme a une complexité en temps de O( 𝑵​),
où N est le nombre à factoriser, car il faut potentiellement tester tous les
nombres jusqu’à la racine carrée quadratique de N. Il a aussi une
complexité en espace de O(1), car il n’utilise pas de mémoire
supplémentaire.
PARAMETRE DE COMPLEXITÉ
• On voit donc que la factorisation par la méthode de Fermat est plus
efficace que la factorisation naïve en termes de complexité en temps,
mais qu’elle nécessite que le nombre à factoriser soit impair. La
complexité algorithmique permet donc de comparer ces deux
algorithmes, et de choisir le plus adapté selon le contexte.
COMPLEXITE EN TEMPS ET EN ESPACE
• La complexité en temps d’un algorithme est le nombre d’opérations
élémentaires (affectations, comparaisons, additions, etc.) que l’algorithme
effectue pour résoudre un problème de taille N, où N est le paramètre de
complexité du problème. La complexité en temps permet de mesurer la
rapidité d’un algorithme, c’est-à-dire le temps d’exécution nécessaire pour
obtenir le résultat.
• Par exemple, si un algorithme effectue N opérations pour résoudre un
problème de taille N, sa complexité en temps est N. Si un algorithme
effectue N² opérations pour résoudre un problème de taille N, sa
complexité en temps est N².
COMPLEXITE EN TEMPS ET EN ESPACE
• La complexité en espace d’un algorithme est la quantité de mémoire
(variables, tableaux, piles, etc.) que l’algorithme utilise pour résoudre un
problème de taille N, où N est le paramètre de complexité du problème. La
complexité en espace permet de mesurer la consommation de mémoire
d’un algorithme, c’est-à-dire l’espace mémoire nécessaire pour stocker les
données et les instructions.
• Par exemple, si un algorithme utilise N variables pour résoudre un
problème de taille N, sa complexité en espace est N. Si un algorithme
utilise N² cases d’un tableau pour résoudre un problème de taille N, sa
complexité en espace est N².
COMPLEXITE EN TEMPS ET EN ESPACE
• La complexité en temps et en espace sont souvent liées, car un algorithme
qui utilise plus de mémoire peut être plus rapide qu’un algorithme qui
utilise moins de mémoire, et inversement. Il faut donc trouver un
compromis entre les deux, selon les contraintes du problème et les
ressources disponibles.
• Par exemple, un algorithme qui trie un tableau en utilisant un autre tableau
auxiliaire peut être plus rapide qu’un algorithme qui trie le tableau sur
place, mais il utilise plus de mémoire. Un algorithme qui utilise la
récursivité peut être plus élégant qu’un algorithme qui utilise une boucle,
mais il utilise plus de pile.
COMPLEXITE EN TEMPS ET EN ESPACE
• Pour illustrer le concept de complexité en temps et en espace, prenons l’exemple du
problème du tri d’un tableau. Il existe plusieurs algorithmes qui permettent de résoudre
ce problème, mais ils n’ont pas la même complexité.
• Par exemple, l’algorithme du tri par sélection consiste à chercher le plus petit élément
du tableau, et à l’échanger avec le premier élément. Puis, on répète le processus sur le
reste du tableau, jusqu’à ce qu’il soit trié. Cet algorithme a une complexité en temps
de O(N²), où N est la taille du tableau, car il faut effectuer N-1 sélections, et chaque
sélection nécessite de parcourir N-i éléments, où i est le rang de la sélection.
• Il a aussi une complexité en espace de O(1), car il n’utilise pas de mémoire
supplémentaire.
COMPLEXITE EN TEMPS ET EN ESPACE
• Un autre algorithme qui permet de résoudre le même problème est le tri
fusion. Cet algorithme utilise le principe du diviser pour régner. Il consiste
à diviser le tableau en deux sous-tableaux de taille égale, à trier
récursivement ces deux sous-tableaux, puis à fusionner les deux sous-
tableaux triés en un seul tableau trié. Cet algorithme a une complexité en
temps de O(N log N), où N est la taille du tableau, car il divise le problème
par deux à chaque étape, et chaque fusion nécessite de parcourir N
éléments. Il a aussi une complexité en espace de O(N), car il utilise un
tableau auxiliaire de taille N pour la fusion.
CALCUL DE LA COMPLEXITE
• Le calcul de complexité d’un algorithme consiste à déterminer la
fonction qui exprime la complexité en temps ou en espace de
l’algorithme en fonction du paramètre de complexité du problème. Le
calcul de complexité permet de quantifier la performance d’un
algorithme, et de la comparer à celle d’autres algorithmes.
CALCUL DE LA COMPLEXITE
• Le calcul de complexité se fait généralement par analyse ou
par expérimentation. L’analyse consiste à examiner le code de
l’algorithme, et à compter le nombre d’opérations élémentaires ou le
nombre d’unités de mémoire utilisées par l’algorithme pour
différentes valeurs du paramètre de complexité. L’expérimentation
consiste à exécuter l’algorithme sur des données de différentes tailles,
et à mesurer le temps d’exécution ou la consommation de mémoire
de l’algorithme.
CALCUL DE LA COMPLEXITE
• L’analyse et l’expérimentation ont chacune leurs avantages et leurs
inconvénients. L’analyse permet d’obtenir une formule mathématique qui
décrit la complexité de l’algorithme, mais elle peut être difficile à réaliser,
et elle peut négliger certains facteurs qui influencent la performance de
l’algorithme, comme le langage de programmation, le compilateur, ou le
processeur. L’expérimentation permet d’obtenir des résultats concrets et
précis, mais elle nécessite de disposer de données suffisamment variées et
représentatives, et elle peut être sensible aux conditions d’exécution,
comme la charge du système, la température, ou la fréquence d’horloge.
CALCUL DE LA COMPLEXITE
• Pour illustrer le concept de calcul de complexité, prenons l’exemple
du problème de la recherche d’un élément dans un tableau. Nous
avons vu que la recherche séquentielle a une complexité en temps de
O(N), et que la recherche dichotomique a une complexité en temps
de O(log2N), où N est la taille du tableau. Comment peut-on calculer
ces complexités ?
CALCUL DE LA COMPLEXITE
• Pour la recherche séquentielle, on peut procéder par analyse. On
examine le code de l’algorithme, et on compte le nombre
d’opérations élémentaires effectuées par l’algorithme pour une valeur
donnée de N. Par exemple, pour N = 10, on a :
CALCUL DE LA COMPLEXITE
1. Algorithme RechercheSequentielle(T[1..10], x)
2. Variables
3. i : entier // indice de parcours du tableau
4. pos : entier // position de x dans T
5. Début
6. i ← 1 // 1 affectation
7. pos ← -1 // 1 affectation
8. Tant que i ≤ 10 et pos = -1 faire // au plus 10 comparaisons et 9 conjonctions
9. Si T[i] = x alors // au plus 10 comparaisons
10. pos ← i // au plus 1 affectation
11. Fin si
12. i ← i + 1 // au plus 10 additions et 10 affectations
13. Fin tant que
14. Renvoyer pos // 1 renvoi
15. Fin
CALCUL DE LA COMPLEXITE
On voit que le nombre d’opérations élémentaires effectuées par
l’algorithme est au plus 1 + 1 + 10 + 9 + 10 + 1 + 10 + 10 + 10 + 1 = 63.
On peut généraliser ce résultat pour une valeur quelconque de N, en
remplaçant 10 par N. On obtient alors que le nombre d’opérations
élémentaires est au plus 1 + 1 + N + N-1 + N + 1 + N + N + N + 1 = 6N +
6. On peut donc dire que la complexité en temps de la recherche
séquentielle est 6N + 6.
CALCUL DE LA COMPLEXITE
Pour la recherche dichotomique, on peut procéder par
expérimentation. On exécute l’algorithme sur des tableaux de
différentes tailles, et on mesure le temps d’exécution de l’algorithme
pour chaque taille. Par exemple, on peut obtenir les résultats suivants :
CALCUL DE LA COMPLEXITE
Taille du tableau (N) Temps d’exécution (en millisecondes)
10 0.1
100 0.2
1000 0.3
10000 0.4
100000 0.5
CALCUL DE LA COMPLEXITE
On voit que le temps d’exécution augmente très lentement avec la
taille du tableau. On peut essayer de trouver une fonction qui modélise
le comportement de l’algorithme, en utilisant la méthode de régression
linéaire, qui consiste à trouver la droite qui minimise la distance entre
les points de données et la droite elle-même. La droite de régression a
pour équation y=a+bx, où y est le temps d’exécution, x est la taille du
tableau, et a et b sont les coefficients à déterminer.
CALCUL DE LA COMPLEXITE
Pour calculer les coefficients a et b, on peut utiliser la formule suivante
:

𝑛 σ 𝑥𝑦 − σ 𝑥 σ 𝑦
𝑏=
𝑛 σ 𝑥 2 − (σ 𝑥)2
σ𝑦 − 𝑏σ𝑥
𝑎=
𝑛
où n est le nombre de points de données, et ∑ désigne la somme sur
tous les points.
CALCUL DE LA COMPLEXITE
En appliquant cette formule aux données fournies, on obtient :
y=-0,0005+4,999*10-6x
Cette fonction est cohérente avec la complexité théorique de l’algorithme de
recherche dichotomique, qui est O(log2​n). En effet, le coefficient b est
proche de (1/log210)​≈3.322×10−6, ce qui signifie que le temps d’exécution
augmente proportionnellement au logarithme de la taille du tableau.
On peut donc en conclure que

log10 𝑁
𝑓 𝑁 = −0.0005 + log 2 𝑁 (𝑐𝑎𝑟 log 2 𝑁 = )
log10 2
COMPLEXITE AU PIRE ET AU MEILLEUR
La complexité au pire et au meilleur d’un algorithme est la complexité
en temps ou en espace de l’algorithme pour les cas les plus
défavorables ou les plus favorables du problème. La complexité au pire
et au meilleur permet de mesurer les limites inférieures et supérieures
de la performance d’un algorithme, c’est-à-dire le temps ou l’espace
minimal et maximal nécessaires pour résoudre le problème.
COMPLEXITE AU PIRE ET AU MEILLEUR
La complexité au pire d’un algorithme est la complexité en temps ou en
espace de l’algorithme pour les cas les plus difficiles du problème, c’est-
à-dire ceux qui nécessitent le plus de temps ou d’espace pour être
résolus. La complexité au pire permet de garantir que l’algorithme ne
dépassera pas une certaine limite de temps ou d’espace, quelles que
soient les données du problème. La complexité au pire est souvent
utilisée pour évaluer la fiabilité et la robustesse d’un algorithme, car
elle indique le comportement le plus mauvais de l’algorithme.
COMPLEXITE AU PIRE ET AU MEILLEUR
La complexité au meilleur d’un algorithme est la complexité en temps
ou en espace de l’algorithme pour les cas les plus faciles du problème,
c’est-à-dire ceux qui nécessitent le moins de temps ou d’espace pour
être résolus. La complexité au meilleur permet d’indiquer que
l’algorithme peut être très rapide ou très économe en mémoire, dans
certaines situations favorables. La complexité au meilleur est souvent
utilisée pour comparer des algorithmes qui ont la même complexité au
pire, mais qui peuvent avoir des comportements différents selon les
données du problème.
EXEMPLE DE COMPLEXITE – RECHERCHE SEQUENTIELLE
La recherche séquentielle est un algorithme qui permet de
chercher un élément dans un tableau non trié. Il consiste à
parcourir le tableau du début à la fin, et à comparer chaque
élément avec l’élément recherché. Si on trouve l’élément, on
arrête le parcours et on renvoie sa position. Sinon, on renvoie -1.
EXEMPLE DE COMPLEXITE – RECHERCHE SEQUENTIELLE
Algorithme RechercheSequentielle(T[1..N], x) Tant que i ≤ N et pos = -1 faire // on parcourt
Variables le tableau tant qu'on n'a pas trouvé x
Si T[i] = x alors // si on trouve x
i : entier // indice de parcours du tableau
pos ← i // on met à jour la position
pos : entier // position de x dans T
Fin si
Début i ← i + 1 // on passe à l'élément suivant
i ← 1 // on commence par le premier Fin tant que
élément Renvoyer pos // on renvoie la position de x
pos ← -1 // on initialise la position à -1 Fin
EXEMPLE DE COMPLEXITE – RECHERCHE SEQUENTIELLE
La complexité en temps de la recherche séquentielle dépend du nombre d’éléments
à examiner avant de trouver l’élément recherché, ou de constater qu’il n’est pas
dans le tableau.
Au pire cas, si l’élément n’est pas dans le tableau, ou s’il est le dernier élément du
tableau, il faut examiner tous les N éléments du tableau. La complexité en temps
est donc O(N) au pire cas.
Au meilleur cas, si l’élément est le premier élément du tableau, il suffit d’examiner
un seul élément. La complexité en temps est donc O(1) au meilleur cas.
En moyenne, si on suppose que l’élément recherché a la même probabilité d’être à
n’importe quelle position du tableau, il faut examiner N/2 éléments en moyenne. La
complexité en temps est donc O(N) en moyenne.
EXEMPLE DE COMPLEXITE – RECHERCHE SEQUENTIELLE
La complexité en espace de la recherche séquentielle est
constante, car l’algorithme n’utilise pas de mémoire
supplémentaire, à part deux variables i et pos. La complexité en
espace est donc O(1).
EXEMPLE DE COMPLEXITE – RECHERCHE DICHOTOMIQUE
La recherche dichotomique est un algorithme qui permet de
chercher un élément dans un tableau trié. Il utilise le principe du
diviser pour régner. Il consiste à comparer l’élément recherché
avec celui du milieu du tableau. Si c’est le même, on renvoie sa
position. Sinon, on réduit le tableau à la moitié qui contient
l’élément recherché, et on recommence le processus.
EXEMPLE DE COMPLEXITE – RECHERCHE DICHOTOMIQUE
La recherche dichotomique est un algorithme qui permet de
chercher un élément dans un tableau trié. Il utilise le principe du
diviser pour régner. Il consiste à comparer l’élément recherché
avec celui du milieu du tableau. Si c’est le même, on renvoie sa
position. Sinon, on réduit le tableau à la moitié qui contient
l’élément recherché, et on recommence le processus.
EXEMPLE DE COMPLEXITE – RECHERCHE DICHOTOMIQUE
Algorithme RechercheDichotomique(T[1..N], x) Tant que debut ≤ fin et pos = -1 faire // on Sinon // si x est plus grand que l'élément du
Variables répète le processus tant qu'on n'a pas milieu
debut : entier // indice de début du sous-tableau trouvé x ou que le sous-tableau est vide debut ← milieu + 1 // on réduit le sous-
fin : entier // indice de fin du sous-tableau milieu ← (debut + fin) div 2 // on calcule tableau à la partie droite
l'indice du milieu Fin si
milieu : entier // indice du milieu du sous-tableau
Si T[milieu] = x alors // si on trouve x Fin tant que
pos : entier // position de x dans T
pos ← milieu // on met à jour la Renvoyer pos // on renvoie la position de x
Début
position
debut ← 1 // on initialise le début à 1 Fin
Sinon si T[milieu] > x alors // si x est plus
fin ← N // on initialise la fin à N petit que l'élément du milieu
pos ← -1 // on initialise la position à -1 fin ← milieu - 1 // on réduit le sous-
tableau à la partie gauche
EXEMPLE DE COMPLEXITE – RECHERCHE DICHOTOMIQUE
La complexité en temps de la recherche dichotomique dépend du
nombre d’étapes nécessaires pour trouver l’élément recherché, ou
pour constater qu’il n’est pas dans le tableau. A chaque étape, on
divise le problème par deux, en réduisant le sous-tableau à la
moitié qui contient l’élément recherché. Au pire cas, si l’élément
n’est pas dans le tableau, ou s’il est à une extrémité du tableau, il
faut répéter le processus jusqu’à ce que le sous-tableau soit vide.
Le nombre d’étapes est donc égal au nombre de fois qu’on peut
diviser N par 2, avant d’obtenir 1. Ce nombre est égal à log N.
EXEMPLE DE COMPLEXITE – RECHERCHE DICHOTOMIQUE
La complexité en temps est donc O(log N) au pire cas.
Au meilleur cas, si l’élément est le milieu du tableau, il suffit d’une
seule étape. La complexité en temps est donc O(1) au meilleur cas.
En moyenne, si on suppose que l’élément recherché a la même
probabilité d’être à n’importe quelle position du tableau, le nombre
d’étapes est égal à la moyenne des nombres d’étapes pour chaque
position. Ce nombre est égal à log N. La complexité en temps est donc
O(log N) en moyenne.
EXEMPLE DE COMPLEXITE – RECHERCHE DICHOTOMIQUE
La complexité en espace de la recherche dichotomique est constante,
car l’algorithme n’utilise pas de mémoire supplémentaire, à part quatre
variables debut, fin, milieu et pos. La complexité en espace est donc
O(1).
EXEMPLE DE COMPLEXITE – TRI PAR SELECTION
Le tri par sélection est un algorithme qui consiste à chercher le plus
petit (ou le plus grand) élément du tableau, et à l’échanger avec le
premier (ou le dernier) élément. Puis, on répète le processus sur le
reste du tableau, jusqu’à ce qu’il soit trié.
EXEMPLE DE COMPLEXITE – TRI PAR SELECTION
Algorithme TriSelection(T[1..N]) Si T[j] < T[min] alors // si on trouve un élément plus petit que le
plus petit élément
Variables
min ← j // on met à jour le plus petit élément
i : entier // indice de la sélection Fin si
j : entier // indice de parcours du tableau Fin pour

min : entier // indice du plus petit élément Si min ≠ i alors // si le plus petit élément n'est pas le premier
élément du sous-tableau
tmp : entier // variable temporaire pour l'échange
tmp ← T[i] // on échange le premier élément et le plus petit
Début élément

Pour i de 1 à N-1 faire // on effectue N-1 sélections T[i] ← T[min]


T[min] ← tmp
min ← i // on initialise le plus petit élément au premier
Fin si
élément du sous-tableau
Fin pour
Pour j de i+1 à N faire // on parcourt le sous-tableau à
Fin
partir du deuxième élément
EXEMPLE DE COMPLEXITE – TRI PAR SELECTION
La complexité en temps du tri par sélection dépend du nombre de
comparaisons et d’échanges effectués par l’algorithme pour trier le
tableau. A chaque sélection, on compare N-i éléments avec le plus petit
élément, et on échange éventuellement deux éléments. Le nombre
total de comparaisons est donc égal à la somme des N-1 premiers
entiers, soit (N-1)N/2. Le nombre total d’échanges est au plus N-1.
La complexité en temps est donc O((N-1)N/2 + N-1) = O(N²) au pire cas,
au meilleur cas, et en moyenne.
EXEMPLE DE COMPLEXITE – TRI PAR SELECTION
La complexité en espace du tri par sélection est constante, car
l’algorithme n’utilise pas de mémoire supplémentaire, à part quatre
variables i, j, min et tmp. La complexité en espace est donc O(1).
EXEMPLE DE COMPLEXITE – TRI PAR INSERTION
Le tri par insertion est un algorithme qui consiste à insérer chaque
élément du tableau à sa place dans le sous-tableau déjà trié qui le
précède. Il commence par le deuxième élément du tableau, et le
compare avec le premier élément. S’il est plus petit, il l’échange avec le
premier élément. Puis, il passe au troisième élément, et le compare
avec les deux premiers éléments, en partant de la fin. S’il est plus petit
que le dernier élément, il l’échange avec lui, et ainsi de suite, jusqu’à ce
qu’il soit à sa place. Puis, il répète le processus avec les éléments
suivants, jusqu’à ce que le tableau soit trié.
EXEMPLE DE COMPLEXITE – TRI PAR INSERTION
Algorithme TriInsertion(T[1..N]) Tant que j > 1 et T[j-1] > tmp faire // on parcourt le
Variables sous-tableau trié en partant de la fin, tant qu'on n'a
pas trouvé la place de l'élément à insérer
i : entier // indice de l'insertion
T[j] ← T[j-1] // on décale les éléments plus grands
j : entier // indice de parcours du sous-tableau
que l'élément à insérer vers la droite
tmp : entier // variable temporaire pour l'échange
j ← j - 1 // on passe à l'élément précédent
Début
Fin tant que
Pour i de 2 à N faire // on effectue N-1 insertions
T[j] ← tmp // on insère l'élément à sa place
tmp ← T[i] // on mémorise l'élément à insérer
Fin pour
j ← i // on initialise j à la position de l'élément à
Fin
insérer
EXEMPLE DE COMPLEXITE – TRI PAR INSERTION
La complexité en temps du tri par insertion dépend du nombre de
comparaisons et de décalages effectués par l’algorithme pour trier le
tableau. A chaque insertion, on compare l’élément à insérer avec les
éléments du sous-tableau trié qui le précède, en partant de la fin,
jusqu’à ce qu’on trouve sa place.
Le nombre de comparaisons est donc égal au nombre d’éléments plus
grands que l’élément à insérer, plus un. Le nombre de décalages est
égal au nombre de comparaisons, moins un.
EXEMPLE DE COMPLEXITE – TRI PAR INSERTION
Au pire cas, si le tableau est trié dans l’ordre décroissant, il faut
comparer et décaler tous les éléments du sous-tableau trié à chaque
insertion. Le nombre total de comparaisons est donc égal à la somme
des N-1 premiers entiers, soit (N-1)N/2. Le nombre total de décalages
est égal au nombre total de comparaisons, moins N-1, soit (N-2)(N-
1)/2. La complexité en temps est donc O((N-1)N/2 + (N-2)(N-1)/2) =
O(N²) au pire cas.
EXEMPLE DE COMPLEXITE – TRI PAR INSERTION
Au meilleur cas, si le tableau est déjà trié dans l’ordre croissant, il suffit de
comparer chaque élément avec le dernier élément du sous-tableau trié, sans le
décaler. Le nombre total de comparaisons est donc égal à N-1, et le nombre total de
décalages est égal à 0. La complexité en temps est donc O(N-1) = O(N) au meilleur
cas.
En moyenne, si on suppose que les éléments du tableau sont répartis
aléatoirement, il faut comparer et décaler la moitié des éléments du sous-tableau
trié à chaque insertion. Le nombre total de comparaisons est donc égal à la moitié
de la somme des N-1 premiers entiers, soit (N-1)N/4. Le nombre total de décalages
est égal au nombre total de comparaisons, moins N-1, soit (N-2)(N-1)/4. La
complexité en temps est donc O((N-1)N/4 + (N-2)(N-1)/4) = O(N²) en moyenne.
EXEMPLE DE COMPLEXITE – TRI PAR INSERTION
La complexité en espace du tri par insertion est constante, car
l’algorithme n’utilise pas de mémoire supplémentaire, à part trois
variables i, j et tmp. La complexité en espace est donc O(1).
EXEMPLE DE COMPLEXITE – RECURSIVITE
La récursivité est une technique de programmation qui consiste à définir une
fonction ou une procédure qui s’appelle elle-même, directement ou
indirectement. La récursivité permet de résoudre des problèmes qui ont une
structure répétitive ou hiérarchique, en utilisant le principe du diviser pour
régner. La récursivité nécessite de définir un ou plusieurs cas de base, qui
sont les cas les plus simples du problème, et qui ne nécessitent pas d’appel
récursif. La récursivité nécessite aussi de définir un ou plusieurs cas récursifs,
qui sont les cas plus complexes du problème, et qui se ramènent à des cas
plus simples par des appels récursifs.
EXEMPLE DE COMPLEXITE – RECURSIVITE
La complexité en temps et en espace d’un algorithme récursif dépend
du nombre et de la taille des appels récursifs effectués par l’algorithme
pour résoudre le problème. A chaque appel récursif, on utilise du
temps pour exécuter les instructions de la fonction ou de la procédure,
et on utilise de la mémoire pour stocker les paramètres et les variables
locales. Le nombre et la taille des appels récursifs dépendent de la
façon dont le problème est divisé en sous-problèmes, et de la façon
dont les solutions des sous-problèmes sont combinées pour obtenir la
solution du problème.
EXEMPLE DE COMPLEXITE – RECURSIVITE
Pour illustrer le concept de récursivité, prenons l’exemple du problème
du calcul de la factorielle d’un nombre entier positif. La factorielle d’un
nombre n, notée n!, est le produit de tous les entiers de 1 à n. Par
exemple, 5! = 1 x 2 x 3 x 4 x 5 = 120. Il existe un algorithme itératif qui
permet de résoudre ce problème, en utilisant une boucle.
EXEMPLE DE COMPLEXITE – RECURSIVITE
Algorithme FactorielleIteratif(n)
Variables
i : entier // indice de parcours de la boucle
f : entier // variable pour stocker la factorielle
Début
f ← 1 // on initialise la factorielle à 1
Pour i de 1 à n faire // on parcourt les entiers de 1 à n
f ← f * i // on multiplie la factorielle par i
Fin pour
Renvoyer f // on renvoie la factorielle
Fin
EXEMPLE DE COMPLEXITE – RECURSIVITE
La complexité en temps de cet algorithme est proportionnelle au
nombre de multiplications effectuées par l’algorithme pour calculer la
factorielle. A chaque itération, on effectue une multiplication. Le
nombre total de multiplications est donc égal à n. La complexité en
temps est donc O(n) au pire cas, au meilleur cas, et en moyenne.
EXEMPLE DE COMPLEXITE – RECURSIVITE
La complexité en espace de cet algorithme est constante, car l’algorithme
n’utilise pas de mémoire supplémentaire, à part deux variables i et f. La
complexité en espace est donc O(1).
Il existe aussi un algorithme récursif qui permet de résoudre ce problème, en
utilisant la propriété suivante : n! = n x (n-1)!. Par exemple, 5! = 5 x 4!. Cette
propriété permet de définir la factorielle de n en fonction de la factorielle de
n-1, qui est un cas plus simple. Le cas de base est la factorielle de 0, qui est
égale à 1 par convention. Voici l’algorithme récursif du calcul de la factorielle
:
EXEMPLE DE COMPLEXITE – RECURSIVITE
Algorithme FactorielleRecursif(n)
Début
Si n = 0 alors // cas de base
Renvoyer 1 // 0! = 1
Sinon // cas récursif
Renvoyer n * FactorielleRecursif(n-1) // n! = n x (n-1)!
Fin si
Fin
EXEMPLE DE COMPLEXITE – RECURSIVITE
La complexité en temps de cet algorithme est proportionnelle au
nombre d’appels récursifs effectués par l’algorithme pour calculer la
factorielle. A chaque appel récursif, on effectue une multiplication. Le
nombre total d’appels récursifs est donc égal à n+1, car il faut compter
l’appel initial. Le nombre total de multiplications est donc égal à n. La
complexité en temps est donc O(n+1) = O(n) au pire cas, au meilleur
cas, et en moyenne.
EXEMPLE DE COMPLEXITE – RECURSIVITE
La complexité en espace de cet algorithme est proportionnelle à la
profondeur de la récursion. A chaque appel récursif, on utilise de la
mémoire pour stocker le paramètre et la variable locale. La profondeur
de la récursion est égale au nombre d’appels récursifs, soit n+1. La
complexité en espace est donc O(n+1) = O(n) au pire cas, au meilleur
cas, et en moyenne.
EXEMPLE DE COMPLEXITE – TRI RAPIDE
Le tri rapide est un algorithme qui utilise le principe du diviser pour
régner. Il consiste à choisir un élément du tableau, appelé pivot, et à
répartir les autres éléments du tableau en deux sous-tableaux, l’un
contenant les éléments plus petits ou égaux au pivot, et l’autre
contenant les éléments plus grands que le pivot. Puis, on trie
récursivement ces deux sous-tableaux, et on concatène le premier
sous-tableau, le pivot, et le deuxième sous-tableau, pour obtenir le
tableau trié.
EXEMPLE DE COMPLEXITE – TRI RAPIDE
Algorithme TriRapide(T[1..N]) Début Fin si
Début Si debut < fin alors
Fin tant que
TriRapideRec(T, 1, N) // on appelle la fonction pivot ← T[debut]
récursive tmp ← T[debut]
i ← debut + 1
Fin T[debut] ← T[j]
j ← fin
T[j] ← tmp
Tant que i ≤ j faire
Fonction TriRapideRec(T[1..N], debut, fin) TriRapideRec(T, debut, j-1) // on trie
Tant que i ≤ j et T[j] > pivot faire
// Trie le sous-tableau T[debut..fin] par ordre récursivement la partie gauche du sous-
croissant j←j-1 tableau
Variables Fin tant que TriRapideRec(T, j+1, fin) // on trie
pivot : entier // valeur du pivot Si i < j alors récursivement la partie droite du sous-
i : entier // indice de parcours du sous-tableau tmp ← T[i] tableau
j : entier // indice de parcours du sous-tableau T[i] ← T[j] Fin si
tmp : entier // variable temporaire pour l'échange
T[j] ← tmp Fin
EXEMPLE DE COMPLEXITE – TRI RAPIDE
La complexité en temps du tri rapide dépend du choix du pivot et de la
répartition des éléments dans les deux sous-tableaux. A chaque étape, on
compare et on échange N-1 éléments avec le pivot, et on divise le problème
en deux sous-problèmes de tailles variables.
Au pire cas, si le pivot est le plus petit ou le plus grand élément du tableau, il
n’y a pas de répartition équilibrée, et le sous-tableau le plus grand a une
taille de N-1. Le nombre total de comparaisons et d’échanges est donc égal à
la somme des N-1 premiers entiers, soit (N-1)N/2. La complexité en temps
est donc O((N-1)N/2) = O(N²) au pire cas.
EXEMPLE DE COMPLEXITE – TRI RAPIDE
Au meilleur cas, si le pivot est le médian du tableau, il y a une répartition
équilibrée, et les deux sous-tableaux ont une taille de N/2. Le nombre total
de comparaisons et d’échanges est donc égal à N-1 à chaque étape, et le
nombre d’étapes est égal à log N. La complexité en temps est donc O((N-1)
log N) = O(N log N) au meilleur cas.
En moyenne, si on suppose que le pivot est choisi aléatoirement, il y a une
répartition raisonnablement équilibrée, et les deux sous-tableaux ont une
taille de l’ordre de N/2. Le nombre total de comparaisons et d’échanges est
donc égal à N-1 à chaque étape, et le nombre d’étapes est égal à log N. La
complexité en temps est donc O((N-1) log N) = O(N log N) en moyenne.
EXEMPLE DE COMPLEXITE – TRI RAPIDE
La complexité en espace du tri rapide dépend de la profondeur de la récursion. A chaque
appel récursif, on utilise de la mémoire pour stocker les paramètres et les variables locales.
Au pire cas, si le pivot est le plus petit ou le plus grand élément du tableau, il n’y a pas de
répartition équilibrée, et la profondeur de la récursion est égale à N. La complexité en
espace est donc O(N) au pire cas.
Au meilleur cas, si le pivot est le médian du tableau, il y a une répartition équilibrée, et la
profondeur de la récursion est égale à log N. La complexité en espace est donc O(log N) au
meilleur cas.
En moyenne, si on suppose que le pivot est choisi aléatoirement, il y a une répartition
raisonnablement équilibrée, et la profondeur de la récursion est égale à log N. La
complexité en espace est donc O(log N) en moyenne.
EXEMPLE DE COMPLEXITE – TRI RAPIDE
On voit donc que le tri rapide et le calcul de la factorielle sont des
exemples de problèmes qui peuvent être résolus par des algorithmes
récursifs. Cependant, ils n’ont pas la même complexité en temps et en
espace, selon la façon dont le problème est divisé et combiné. La
récursivité peut être plus élégante et plus simple que l’itération, mais
elle peut aussi être plus coûteuse en mémoire et en temps d’exécution.
Il faut donc choisir la technique la plus adaptée selon le problème et les
ressources disponibles.
COMPLEXITE ASYMPTOTIQUE
La complexité asymptotique d’un algorithme est le comportement de la
complexité en temps ou en espace lorsque le paramètre de complexité
tend vers l’infini. Autrement dit, c’est la façon dont la complexité
évolue lorsque le problème devient de plus en plus grand.
La complexité asymptotique permet d’ignorer les détails insignifiants,
tels que les constantes ou les termes de faible ordre, et de se
concentrer sur l’ordre de grandeur de la complexité.
COMPLEXITE ASYMPTOTIQUE
La complexité asymptotique est souvent exprimée à l’aide de la
notation de Landau, qui utilise des symboles tels que O, Ω, Θ, o, ω pour
comparer des fonctions.
Par exemple, on dit qu’une fonction f est O(g) si f est bornée (borne
supérieure) par un multiple de g lorsque x tend vers l’infini. Cela signifie
que f ne croît pas plus vite que g.
Par exemple, 3x + 5 est O(x), car 3x + 5 ≤ 4x pour tout x ≥ 5.
COMPLEXITE ASYMPTOTIQUE
COMPLEXITE ASYMPTOTIQUE
La notation de Landau permet de classer les fonctions selon leur vitesse de
croissance, et donc de comparer la complexité de différents algorithmes.
Par exemple, on peut dire que la complexité en temps du tri par sélection, du
tri par insertion, et du tri rapide au pire cas est O(N²), où N est la taille du
tableau à trier. Cela signifie que ces algorithmes ont le même ordre de
grandeur de complexité, et qu’ils sont plus lents que des algorithmes qui ont
une complexité en temps de O(N log N), comme le tri rapide au meilleur cas
et en moyenne, ou le tri fusion.
COMPLEXITE ASYMPTOTIQUE
La notation de Landau a plusieurs propriétés qui facilitent son utilisation. Par
exemple, on peut dire que :
• Si f est O(g) et g est O(h), alors f est O(h). Cela signifie que la relation O est
transitive, et qu’on peut comparer des fonctions en chaînant des symboles O.
• Par exemple, si f(x) = x² + x, g(x) = x², et h(x) = x³, alors on peut dire que f est O(g),
car f(x) / g(x) = (x² + x) / x² tend vers 1 lorsque x tend vers l’infini. On peut aussi
dire que g est O(h), car g(x) / h(x) = x² / x³ = 1 / x tend vers 0 lorsque x tend vers
l’infini. Par conséquent, on peut dire que f est O(h), car f(x) / h(x) = (x² + x) / x³ =
(1 + 1 / x) / x tend vers 0 lorsque x tend vers l’infini.
COMPLEXITE ASYMPTOTIQUE
• Si f est O(g) et c est une constante, alors c.f est O(g) et f/c est O(g). Cela
signifie que la relation O est indépendante des constantes, et qu’on peut
les ignorer dans les fonctions.
• Par exemple, si f(x) = x² et g(x) = x³, alors on peut dire que f est O(g), car f(x)
/ g(x) = x² / x³ = 1 / x tend vers 0 lorsque x tend vers l’infini. Si on prend c =
2, alors on peut dire que c.f est O(g), car (c.f)(x) / g(x) = (2.f)(x) / g(x) = 2.f(x)
/ g(x) = 2 / x tend vers 0 lorsque x tend vers l’infini. On peut aussi dire que
f/c est O(g), car (f/c)(x) / g(x) = (f/2)(x) / g(x) = f(x) / (2.g)(x) = f(x) / g(x) / 2 =
1 / (2.x) tend vers 0 lorsque x tend vers l’infini.
COMPLEXITE ASYMPTOTIQUE
• Si f et g sont O(h), alors f + g est O(h) et f.g est O(h²). Cela signifie que la
relation O est compatible avec les opérations arithmétiques, et qu’on peut
les appliquer aux fonctions.
• Par exemple, si f(x) = x², g(x) = x, et h(x) = x³, alors on peut dire que f et g
sont O(h), car f(x) / h(x) = x² / x³ = 1 / x tend vers 0 lorsque x tend vers
l’infini, et g(x) / h(x) = x / x³ = 1 / x² tend vers 0 lorsque x tend vers l’infini.
On peut aussi dire que f + g est O(h), car (f + g)(x) / h(x) = (x² + x) / x³ = (1 +
1 / x) / x tend vers 0 lorsque x tend vers l’infini. On peut enfin dire que f.g
est O(h²), car (f.g)(x) / h²(x) = (x².x)(x) / x⁶ = x³ / x⁶ = 1 / x³ tend vers 0
lorsque x tend vers l’infini.
COMPLEXITE ASYMPTOTIQUE
La notation de Landau utilise différents symboles pour exprimer
différents types de comparaison entre les fonctions. Par exemple, on
peut dire que :
• O(g) est l’ensemble des fonctions qui sont bornées par un multiple de
g lorsque x tend vers l’infini. Cela signifie que f est O(g) si il existe une
constante c > 0 et un réel x0 tel que pour tout x ≥ x0, on ait f(x) ≤
c.g(x)
COMPLEXITE ASYMPTOTIQUE
• Ω(g) est l’ensemble des fonctions qui sont supérieures à un multiple
de g lorsque x tend vers l’infini. Cela signifie que f est Ω(g) si il existe
une constante c > 0 et un réel x0 tel que pour tout x ≥ x0, on ait f(x) ≥
c.g(x).
• Un exemple de fonction Ω(g) est f(x) = x² et g(x) = x, car f(x) / g(x) = x²
/ x = x tend vers l’infini lorsque x tend vers l’infini. On peut donc dire
que f(x) est supérieure à un multiple de g(x) lorsque x est assez grand.
On écrit f(x) = Ω(g(x)).
COMPLEXITE ASYMPTOTIQUE
• Θ(g) est l’ensemble des fonctions qui sont à la fois O(g) et Ω(g). Cela
signifie que f est Θ(g) si il existe deux constantes c1 > 0 et c2 > 0 et un
réel x0 tel que pour tout x ≥ x0, on ait c1.g(x) ≤ f(x) ≤ c2.g(x)
• Un exemple de fonction Θ(g) est f(x) = 2x² + 3x + 4 et g(x) = x², car f(x)
/ g(x) = (2x² + 3x + 4) / x² tend vers 2 lorsque x tend vers l’infini. On
peut donc dire que f(x) est comprise entre deux multiples de g(x)
lorsque x est assez grand. On écrit f(x) = Θ(g(x)).
COMPLEXITE ASYMPTOTIQUE
• o(g) est l’ensemble des fonctions qui sont négligeables par rapport à g
lorsque x tend vers l’infini. Cela signifie que f est o(g) si pour toute
constante c > 0, il existe un réel x0 tel que pour tout x ≥ x0, on ait f(x)
< c.g(x)
• Un exemple de fonction o(g) est f(x) = x et g(x) = x², car f(x) / g(x) = x /
x² = 1 / x tend vers 0 lorsque x tend vers l’infini. On peut donc dire
que f(x) est négligeable par rapport à g(x) lorsque x est assez grand.
On écrit f(x) = o(g(x)).
COMPLEXITE ASYMPTOTIQUE
• ω(g) est l’ensemble des fonctions qui sont dominantes par rapport à g
lorsque x tend vers l’infini. Cela signifie que f est ω(g) si pour toute
constante c > 0, il existe un réel x0 tel que pour tout x ≥ x0, on ait f(x)
> c.g(x)
• Un exemple de fonction ω(g) est f(x) = x² et g(x) = x, car f(x) / g(x) = x²
/ x = x tend vers l’infini lorsque x tend vers l’infini. On peut donc dire
que f(x) est dominante par rapport à g(x) lorsque x est assez grand.
On écrit f(x) = ω(g(x)).
COMPLEXITE ASYMPTOTIQUE
La notation de Landau permet de décrire la complexité asymptotique d’un
algorithme avec plus ou moins de précision, selon le symbole utilisé. Par exemple,
on peut dire que :
• La complexité en temps du tri par sélection est O(N²), Ω(N²), et Θ(N²), où N est la
taille du tableau à trier. Cela signifie que la complexité en temps du tri par
sélection est exactement de l’ordre de N², et qu’elle ne dépend pas des données
du problème.
• La complexité en temps du tri par insertion est O(N²), Ω(N), et o(N²), où N est la
taille du tableau à trier. Cela signifie que la complexité en temps du tri par
insertion est au plus de l’ordre de N², au moins de l’ordre de N, et négligeable par
rapport à N², et qu’elle dépend des données du problème.
COMPLEXITE ASYMPTOTIQUE
• La complexité en temps du tri rapide est O(N²), Ω(N log N), et ω(N log
N), où N est la taille du tableau à trier. Cela signifie que la complexité
en temps du tri rapide est au plus de l’ordre de N², au moins de
l’ordre de N log N, et dominante par rapport à N log N, et qu’elle
dépend du choix du pivot.
CLASSE DE COMPLEXITE
Les classes de complexité sont des ensembles d’algorithmes qui ont la
même complexité asymptotique, à un facteur constant près. Par
exemple, la classe O(N) regroupe tous les algorithmes qui ont une
complexité en temps ou en espace qui est bornée par un multiple de N,
où N est le paramètre de complexité. Les classes de complexité
permettent de classer les algorithmes selon leur efficacité, et de les
comparer entre eux.
CLASSE DE COMPLEXITE
• O(1) : la complexité est constante, indépendante de la taille du
problème. Par exemple, accéder à un élément d’un tableau, ou
effectuer une opération arithmétique simple, sont des opérations de
complexité O(1). Cela signifie que le temps ou l’espace nécessaires
pour exécuter ces opérations ne varient pas en fonction de la valeur
de N.
CLASSE DE COMPLEXITE
Algorithme AccèsTableau(T, i)
// T est un tableau de taille N, i est un indice entre 0 et N-1
Retourner T[i] // complexité O(1)
Fin Algorithme
CLASSE DE COMPLEXITE
• O(log N) : la complexité est logarithmique, c’est-à-dire qu’elle
augmente proportionnellement au logarithme de la taille du
problème. Par exemple, la recherche dichotomique dans un tableau
trié, ou le calcul de la puissance d’un nombre par exponentiation
rapide, sont des algorithmes de complexité O(log N). Cela signifie que
le temps ou l’espace nécessaires pour exécuter ces algorithmes
augmentent très lentement en fonction de la valeur de N.
• Par exemple, si N passe de 1000 à 1000000, le logarithme de N passe
de 10 à 20, soit un facteur 2.
CLASSE DE COMPLEXITE
• O(N) : la complexité est linéaire, c’est-à-dire qu’elle augmente
proportionnellement à la taille du problème. Par exemple, la
recherche linéaire dans un tableau, ou le calcul de la somme des
éléments d’un tableau, sont des algorithmes de complexité O(N). Cela
signifie que le temps ou l’espace nécessaires pour exécuter ces
algorithmes augmentent de façon proportionnelle à la valeur de N.
• Par exemple, si N passe de 1000 à 1000000, le temps ou l’espace
nécessaires sont multipliés par 1000.
CLASSE DE COMPLEXITE
• O(N log N) : la complexité est quasi-linéaire, c’est-à-dire qu’elle
augmente légèrement plus vite que la taille du problème. Par
exemple, le tri fusion ou le tri rapide sont des algorithmes de
complexité O(N log N) dans le cas moyen. Cela signifie que le temps
ou l’espace nécessaires pour exécuter ces algorithmes augmentent de
façon plus rapide que la valeur de N, mais moins rapide que le carré
de N. Par exemple, si N passe de 1000 à 1000000, le temps ou
l’espace nécessaires sont multipliés par environ 2000.
CLASSE DE COMPLEXITE
Algorithme TriFusion(T, i, j)
// T est un tableau de taille N, i et j sont des indices entre 0 et N-1
Si i < j
m <- (i + j) / 2 // indice du milieu
TriFusion(T, i, m) // tri de la partie gauche, complexité O(N log N) / 2
TriFusion(T, m+1, j) // tri de la partie droite, complexité O(N log N) / 2
Fusionner(T, i, m, j) // fusion des deux parties triées, complexité O(N)
Fin Si
Fin Algorithme
CLASSE DE COMPLEXITE
• O(N²) : la complexité est quadratique, c’est-à-dire qu’elle augmente
proportionnellement au carré de la taille du problème. Par exemple,
le tri à bulles ou le tri par sélection sont des algorithmes de
complexité O(N²). Cela signifie que le temps ou l’espace nécessaires
pour exécuter ces algorithmes augmentent de façon très rapide en
fonction de la valeur de N.
• Par exemple, si N passe de 1000 à 1000000, le temps ou l’espace
nécessaires sont multipliés par 1000000.
CLASSE DE COMPLEXITE
Algorithme TriBulles(T)
// T est un tableau de taille N
Pour i allant de 0 à N-2
Pour j allant de 0 à N-2-i
Si T[j] > T[j+1]
Echanger T[j] et T[j+1] // complexité O(1)
Fin Si
Fin Pour
Fin Pour // complexité O(N²)
Fin Algorithme
CLASSE DE COMPLEXITE
• O(2N) : la complexité est exponentielle, c’est-à-dire qu’elle augmente
proportionnellement à une puissance de la taille du problème. Par
exemple, le problème du voyageur de commerce, ou le calcul du
nombre de Fibonacci par récursivité naïve, sont des problèmes de
complexité O(2N). Cela signifie que le temps ou l’espace nécessaires
pour résoudre ces problèmes augmentent de façon astronomique en
fonction de la valeur de N.
• Par exemple, si N passe de 10 à 20, le temps ou l’espace nécessaires
sont multipliés par 1024.
CLASSE DE COMPLEXITE
Algorithme Fibonacci(n)
// n est un entier positif
Si n = 0 ou n = 1
Retourner n // complexité O(1)
Sinon
Retourner Fibonacci(n-1) + Fibonacci(n-2) // complexité O(2n)
Fin Si
Fin Algorithme
EQUIVALENCE ASYMPTOTIQUE
Deux fonctions f et g sont dites asymptotiquement équivalentes si f(x) /
g(x) tend vers 1 lorsque x tend vers l’infini. Cela signifie que f et g ont le
même ordre de grandeur, et que leur rapport tend vers une constante
non nulle. Par exemple, f(x) = 3x + 5 et g(x) = 3x - 2 sont
asymptotiquement équivalentes, car f(x) / g(x) = (3x + 5) / (3x - 2) tend
vers 1 lorsque x tend vers l’infini.
EQUIVALENCE ASYMPTOTIQUE
L’équivalence asymptotique est une relation plus forte que
l’appartenance à la classe Θ. En effet, si f et g sont asymptotiquement
équivalentes, alors f est Θ(g) et g est Θ(f). La réciproque n’est pas vraie
en général. Par exemple, f(x) = x et g(x) = x + ln(x) sont Θ(x), mais pas
asymptotiquement équivalentes, car f(x) / g(x) = x / (x + ln(x)) tend vers
0 lorsque x tend vers l’infini.
EQUIVALENCE ASYMPTOTIQUE
L’équivalence asymptotique permet de simplifier l’expression de la
complexité asymptotique d’un algorithme, en remplaçant une fonction par
une autre qui a le même comportement à l’infini.
Par exemple, si on sait que la complexité en temps d’un algorithme est f(N) =
2N² + 3N + 4, où N est le paramètre de complexité du problème, on peut dire
que la complexité en temps de l’algorithme est asymptotiquement
équivalente à g(N) = 2N², car f(N) / g(N) = (2N² + 3N + 4) / (2N²) tend vers 1
lorsque N tend vers l’infini. On peut donc utiliser g(N) au lieu de f(N) pour
exprimer la complexité en temps de l’algorithme, et dire que la complexité
en temps de l’algorithme est O(N²), Ω(N²), et Θ(N²).
EQUIVALENCE ASYMPTOTIQUE
L’équivalence asymptotique permet aussi de comparer la complexité asymptotique
de différents algorithmes, en utilisant des fonctions simples et représentatives.
Par exemple, si on sait que la complexité en temps du tri par sélection est O(N²), et
que la complexité en temps du tri rapide au meilleur cas est O(N log N), où N est la
taille du tableau à trier, on peut dire que la complexité en temps du tri par sélection
est asymptotiquement supérieure à la complexité en temps du tri rapide au
meilleur cas, car N² / (N log N) tend vers l’infini lorsque N tend vers l’infini. On peut
donc conclure que le tri rapide au meilleur cas est plus rapide que le tri par
sélection, lorsque le problème devient de plus en plus grand.
EQUIVALENCE ASYMPTOTIQUE
Pour illustrer le concept d’équivalence asymptotique, voici quelques
exemples de fonctions qui sont asymptotiquement équivalentes, et d’autres
qui ne le sont pas :
• f(x) = 5x³ + 2x² + 3x + 4 et g(x) = 5x³ sont asymptotiquement équivalentes,
car f(x) / g(x) = (5x³ + 2x² + 3x + 4) / (5x³) tend vers 1 lorsque x tend vers
l’infini.
• f(x) = 2x² + 3x + 4 et g(x) = x² sont asymptotiquement équivalentes, car f(x)
/ g(x) = (2x² + 3x + 4) / (x²) tend vers 2 lorsque x tend vers l’infini.
• f(x) = x² + x et g(x) = x² sont asymptotiquement équivalentes, car f(x) / g(x)
= (x² + x) / (x²) tend vers 1 lorsque x tend vers l’infini.
EQUIVALENCE ASYMPTOTIQUE
• f(x) = x² et g(x) = x sont pas asymptotiquement équivalentes, car f(x) /
g(x) = x² / x = x tend vers l’infini lorsque x tend vers l’infini.
• f(x) = x et g(x) = ln(x) sont pas asymptotiquement équivalentes, car
f(x) / g(x) = x / ln(x) tend vers l’infini lorsque x tend vers l’infini.
• f(x) = ln(x) et g(x) = ln(ln(x)) sont pas asymptotiquement équivalentes,
car f(x) / g(x) = ln(x) / ln(ln(x)) tend vers l’infini lorsque x tend vers
l’infini.
EQUIVALENCE ASYMPTOTIQUE
• Pour visualiser l’équivalence asymptotique, on peut tracer les courbes
des fonctions sur un graphique, et observer leur comportement à
l’infini. Par exemple, voici le graphique des fonctions f(x) = 3x + 5 et
g(x) = 3x - 2, qui sont asymptotiquement équivalentes :
EQUIVALENCE ASYMPTOTIQUE
COMPLEXITE ET PUISSANCE DES MACHINES
• La complexité et la puissance des machines sont deux notions qui
permettent de mesurer les capacités et les limites des modèles de calcul,
c’est-à-dire des systèmes abstraits qui définissent comment effectuer des
opérations et des calculs.
• Par exemple, une machine de Turing est un modèle de calcul qui consiste
en une bande infinie divisée en cases, sur laquelle se déplace une tête de
lecture-écriture qui peut lire, écrire ou effacer des symboles, selon un
ensemble de règles. Une machine de Turing peut simuler n’importe quel
algorithme, et donc n’importe quel autre modèle de calcul.
COMPLEXITE ET PUISSANCE DES MACHINES
• La complexité d’une machine est la mesure du temps ou de l’espace
nécessaires pour qu’une machine résolve un problème donné. La
complexité d’une machine dépend du modèle de calcul utilisé, des
ressources disponibles, et de la difficulté du problème.
• Par exemple, la complexité en temps d’une machine de Turing est le
nombre d’étapes que la machine doit effectuer pour terminer son
calcul, et la complexité en espace d’une machine de Turing est le
nombre de cases que la machine utilise sur sa bande.
COMPLEXITE ET PUISSANCE DES MACHINES
• La puissance d’une machine est la mesure de la classe de problèmes que la
machine peut résoudre. La puissance d’une machine dépend du modèle de
calcul utilisé, des ressources disponibles, et de la décidabilité du problème.
• Par exemple, la puissance d’une machine de Turing est la classe des
problèmes récursivement énumérables, c’est-à-dire les problèmes dont
l’ensemble des solutions peut être énuméré par une machine de Turing. Il
existe des problèmes qui ne sont pas récursivement énumérables, et qui ne
peuvent donc pas être résolus par une machine de Turing, comme le
problème de l’arrêt, qui consiste à déterminer si une machine de Turing
s’arrête ou pas sur une entrée donnée.
COMPLEXITE ET PUISSANCE DES MACHINES
• La complexité et la puissance des machines sont liées, mais pas
équivalentes. En effet, il existe des machines qui ont la même
puissance, mais pas la même complexité, et inversement.
• Par exemple, une machine de Turing déterministe est une machine de
Turing qui n’a qu’une seule règle possible à chaque étape, et une
machine de Turing non déterministe est une machine de Turing qui
peut avoir plusieurs règles possibles à chaque étape, et qui choisit la
meilleure selon le problème.
COMPLEXITE ET PUISSANCE DES MACHINES
• Une machine de Turing déterministe et une machine de Turing non
déterministe ont la même puissance, car elles peuvent résoudre la
même classe de problèmes, mais elles n’ont pas la même complexité,
car une machine de Turing non déterministe peut résoudre certains
problèmes plus rapidement qu’une machine de Turing déterministe,
en explorant plusieurs branches de calcul en parallèle.
COMPLEXITE ET PUISSANCE DES MACHINES
• La complexité et la puissance des machines sont des notions importantes pour
l’informatique théorique, car elles permettent de comprendre les possibilités et les
limites des algorithmes et des ordinateurs.
• Par exemple, la théorie de la complexité étudie les classes de complexité, qui sont des
ensembles de problèmes qui ont la même complexité pour un modèle de calcul donné.
Certaines classes de complexité sont célèbres, comme la classe P, qui contient les
problèmes qui peuvent être résolus en temps polynomial par une machine de Turing
déterministe, ou la classe NP, qui contient les problèmes qui peuvent être vérifiés en
temps polynomial par une machine de Turing déterministe, ou encore la classe NP-
complet, qui contient les problèmes les plus difficiles de la classe NP, et qui sont tels que
si on trouve un algorithme polynomial pour l’un d’entre eux, alors on trouve un
algorithme polynomial pour tous les autres.
COMPLEXITE ET PUISSANCE DES MACHINES
• La question de savoir si P = NP, c’est-à-dire si tous les problèmes de la
classe NP peuvent être résolus en temps polynomial par une machine
de Turing déterministe, est l’un des plus grands mystères de
l’informatique théorique, et a des implications pratiques importantes
pour la cryptographie, l’optimisation, l’intelligence artificielle, et
d’autres domaines.
COMPLEXITE ET PUISSANCE DES MACHINES
Pour illustrer le concept de complexité et de puissance des machines, voici
quelques exemples de problèmes et de machines qui les résolvent :
• Le problème du tri d’un tableau est un problème qui consiste à ranger les
éléments d’un tableau dans un ordre croissant ou décroissant. Le problème du tri
d’un tableau est un problème récursif, c’est-à-dire qu’il peut être résolu par une
machine de Turing. Le problème du tri d’un tableau est aussi un problème
polynomial, c’est-à-dire qu’il peut être résolu en temps polynomial par une
machine de Turing déterministe. Par exemple, le tri fusion est un algorithme qui
résout le problème du tri d’un tableau en temps O(N log N), où N est la taille du
tableau, en utilisant une machine de Turing déterministe.
COMPLEXITE ET PUISSANCE DES MACHINES
• Le problème du voyageur de commerce est un problème qui consiste à trouver le
plus court chemin qui passe par un ensemble de villes, en partant et en revenant
à la même ville. Le problème du voyageur de commerce est un problème récursif,
c’est-à-dire qu’il peut être résolu par une machine de Turing. Le problème du
voyageur de commerce est aussi un problème NP-complet, c’est-à-dire qu’il est
l’un des problèmes les plus difficiles de la classe NP, et qu’il n’existe pas
d’algorithme polynomial qui le résout, sauf si P = NP. Par exemple, l’algorithme du
branch and bound est un algorithme qui résout le problème du voyageur de
commerce en temps O(N!), où N est le nombre de villes, en utilisant une machine
de Turing déterministe.
COMPLEXITE ET PUISSANCE DES MACHINES
• Le problème de la satisfiabilité booléenne est un problème qui consiste à
déterminer si une formule logique composée de variables booléennes et
d’opérateurs logiques peut être rendue vraie en attribuant des valeurs de vérité
aux variables. Le problème de la satisfiabilité booléenne est un problème récursif,
c’est-à-dire qu’il peut être résolu par une machine de Turing. Le problème de la
satisfiabilité booléenne est aussi un problème NP-complet, c’est-à-dire qu’il est
l’un des problèmes les plus difficiles de la classe NP, et qu’il n’existe pas
d’algorithme polynomial qui le résout, sauf si P = NP. Par exemple, l’algorithme
DPLL est un algorithme qui résout le problème de la satisfiabilité booléenne en
temps exponentiel, en utilisant une machine de Turing déterministe.

Vous aimerez peut-être aussi