Fiche récapitulative générée pour impression / export PDF.

Licence 3 · Informatique L3 — Algorithmique, structures de données et graphes

Analyse de la complexité algorithmique

Analyse de la complexité algorithmique

1. Pourquoi mesurer la complexité ?

Pour comparer deux algorithmes résolvant le même problème, on ne mesure pas leur temps d'exécution en secondes (qui dépend de la machine, du langage, etc.) mais le nombre d'opérations élémentaires effectuées, en fonction de la taille nn de l'entrée. C'est la complexité temporelle. On définit de même la complexité spatiale (mémoire utilisée).

2. Notations de Landau : OO, Ω\Omega, Θ\Theta

Soient f,g:NR+f,g:\mathbb{N}\to\mathbb{R}_+. On dit :
- f(n)=O(g(n))f(n)=O(g(n))ff est dominée par gg ») s'il existe c>0c>0 et n0n_0 tels que f(n)cg(n)f(n)\leq c\,g(n) pour tout nn0n\geq n_0majoration asymptotique ;
- f(n)=Ω(g(n))f(n)=\Omega(g(n)) s'il existe c>0,n0c>0,n_0 tels que f(n)cg(n)f(n)\geq c\,g(n) pour nn0n\geq n_0minoration asymptotique ;
- f(n)=Θ(g(n))f(n)=\Theta(g(n)) si f(n)=O(g(n))f(n)=O(g(n)) et f(n)=Ω(g(n))f(n)=\Omega(g(n)) — l'ordre de grandeur exact.

Exemple : f(n)=3n2+5n+2f(n)=3n^2+5n+2. On a f(n)=O(n2)f(n)=O(n^2) (car pour n1n\geq1, 3n2+5n+210n23n^2+5n+2\leq10n^2) et f(n)=Ω(n2)f(n)=\Omega(n^2) (car f(n)3n2f(n)\geq3n^2), donc f(n)=Θ(n2)f(n)=\Theta(n^2) : l'algorithme est de complexité quadratique.

3. Classes de complexité usuelles

Par ordre croissant de coût (pour nn grand) : O(1)O(1) (constant), O(logn)O(\log n) (logarithmique), O(n)O(n) (linéaire), O(nlogn)O(n\log n) (quasi-linéaire), O(n2)O(n^2) (quadratique), O(nk)O(n^k) (polynomiale), O(2n)O(2^n) (exponentielle), O(n!)O(n!) (factorielle).

Exemple typique : la recherche dans un tableau trié par dichotomie est O(logn)O(\log n) ; le tri par insertion est O(n2)O(n^2) dans le pire cas ; le tri fusion est O(nlogn)O(n\log n).

4. Calcul de complexité : boucles et récursivité

Boucles simples : une boucle parcourant nn éléments une fois coûte O(n)O(n). Deux boucles imbriquées, chacune de taille nn, coûtent O(n2)O(n^2) (produit des tailles, dans le cas indépendant).

Récursivité — équation de récurrence. Pour un algorithme récursif qui divise le problème en aa sous-problèmes de taille n/bn/b chacun, avec un coût f(n)f(n) pour combiner les résultats, la complexité T(n)T(n) vérifie :

T(n)=aT(n/b)+f(n)T(n) = a\,T(n/b) + f(n)

Théorème maître (cas simplifié, f(n)=Θ(nd)f(n)=\Theta(n^d)) : en posant p=logbap=\log_b a :
- si d<pd<p : T(n)=Θ(np)T(n)=\Theta(n^p) ;
- si d=pd=p : T(n)=Θ(nplogn)T(n)=\Theta(n^p\log n) ;
- si d>pd>p : T(n)=Θ(nd)T(n)=\Theta(n^d).

Exemple — tri fusion : a=2a=2 sous-tableaux, b=2b=2 (taille n/2n/2 chacun), fusion en f(n)=Θ(n)f(n)=\Theta(n) (donc d=1d=1). On a p=log22=1=dp=\log_2 2=1=d, donc T(n)=Θ(nlogn)T(n)=\Theta(n\log n).

Exemple — recherche dichotomique : a=1a=1 sous-problème de taille n/2n/2, coût de combinaison f(n)=Θ(1)f(n)=\Theta(1) (donc d=0d=0). p=log21=0=dp=\log_2 1=0=d, donc T(n)=Θ(logn)T(n)=\Theta(\log n).

5. Récursivité naïve : Fibonacci

Le calcul naïf de Fib(n)\text{Fib}(n) par double récursion (T(n)=T(n1)+T(n2)+O(1)T(n)=T(n-1)+T(n-2)+O(1)) a une complexité Θ(φn)\Theta(\varphi^n)φ=(1+5)/2\varphi=(1+\sqrt5)/2 est le nombre d'or — exponentielle, car l'arbre des appels recalcule de nombreuses fois les mêmes sous-problèmes. La mémoïsation (stocker les résultats déjà calculés) ramène ce coût à O(n)O(n) : c'est le principe de la programmation dynamique.

6. Pire cas, meilleur cas, cas moyen

On distingue généralement :
- la complexité dans le pire cas : borne valable pour toute entrée de taille nn (la plus utilisée en pratique, car elle garantit une performance) ;
- la complexité dans le meilleur cas : la plus petite complexité possible parmi les entrées de taille nn ;
- la complexité en moyenne : espérance sur une distribution donnée des entrées.

Exemple — tri rapide (quicksort) : Θ(nlogn)\Theta(n\log n) en moyenne, mais Θ(n2)\Theta(n^2) dans le pire cas (pivot systématiquement mal choisi, par exemple sur un tableau déjà trié avec un pivot toujours pris en première position).

7. Récapitulatif


NotationSignification
|---|---|



f=O(g)f=O(g)ff majorée asymptotiquement par gg (à une constante près)
f=Ω(g)f=\Omega(g)ff minorée asymptotiquement par gg
f=Θ(g)f=\Theta(g)ff et gg du même ordre de grandeur
T(n)=aT(n/b)+Θ(nd)T(n)=aT(n/b)+\Theta(n^d)théorème maître : Θ(nmax(d,logba))\Theta(n^{\max(d,\log_ba)}) (avec facteur logn\log n si d=logbad=\log_ba)

Exercices de la leçon

Exercice 1

Que signifie f(n)=O(g(n))f(n)=O(g(n)) ?

Corrigé

La notation OO exprime une majoration asymptotique à une constante multiplicative près, valable seulement à partir d'un certain rang n0n_0 (le comportement pour les petites valeurs de nn n'a pas d'importance).

Exercice 2

Vrai ou faux : un algorithme de complexité O(nlogn)O(n\log n) est, pour nn grand, plus rapide qu'un algorithme de complexité O(n2)O(n^2).

Corrigé

Vrai. La fonction nlognn\log n croît strictement plus lentement que n2n^2 pour nn assez grand (le rapport n2/(nlogn)=n/logn+n^2/(n\log n)=n/\log n\to+\infty), donc un algorithme O(nlogn)O(n\log n) devient toujours plus rapide qu'un O(n2)O(n^2) au-delà d'un certain seuil.

Exercice 3

Quelle est la complexité d'une boucle simple parcourant un tableau de taille nn une seule fois ?

Corrigé

Une boucle qui exécute une opération constante pour chacun des nn éléments du tableau coûte O(n)O(n) : un nombre d'opérations proportionnel à la taille de l'entrée.

Exercice 4

Vrai ou faux : la recherche dichotomique dans un tableau trié de taille nn a une complexité O(logn)O(\log n).

Corrigé

Vrai. À chaque étape, la dichotomie divise par deux la taille de l'espace de recherche, donc le nombre d'étapes nécessaires pour atteindre une taille 11 est log2n\log_2 n.

Exercice 5

Quelle est la complexité, dans le pire cas, du tri rapide (quicksort) ?

Corrigé

Dans le pire cas (pivot systématiquement mal choisi), le tri rapide se dégrade en O(n2)O(n^2), bien que sa complexité moyenne soit O(nlogn)O(n\log n) avec un bon choix de pivot.

Exercice 6

Montrer que f(n)=5n3+2n2+nf(n)=5n^3+2n^2+n est Θ(n3)\Theta(n^3).

Corrigé

Pour n1n\geq1 : 5n3+2n2+n5n3+2n3+n3=8n35n^3+2n^2+n\leq5n^3+2n^3+n^3=8n^3 (majoration, O(n3)O(n^3)) et 5n3+2n2+n5n35n^3+2n^2+n\geq5n^3 (minoration, Ω(n3)\Omega(n^3)). Les deux bornes étant vérifiées, f(n)=Θ(n3)f(n)=\Theta(n^3).

Exercice 7

À l'aide du théorème maître avec a=2,b=2,f(n)=Θ(n)a=2,b=2,f(n)=\Theta(n) (cas d=1d=1), quelle est la complexité du tri fusion ?

Corrigé

p=log22=1=dp=\log_2 2=1=d, donc on est dans le cas d=pd=p du théorème maître : T(n)=Θ(nplogn)=Θ(nlogn)T(n)=\Theta(n^p\log n)=\Theta(n\log n).

Exercice 8

Pour deux boucles imbriquées, la première parcourant nn éléments et la seconde (à l'intérieur) parcourant nn éléments également, quelle est la complexité totale ?

Corrigé

Pour chacune des nn itérations de la boucle externe, la boucle interne s'exécute nn fois, donc le nombre total d'opérations est n×n=n2n\times n=n^2, soit O(n2)O(n^2).

Exercice 9

Vrai ou faux : la complexité dans le pire cas est toujours supérieure ou égale à la complexité en moyenne.

Corrigé

Vrai. La complexité en moyenne est une espérance sur toutes les entrées possibles de taille nn, qui ne peut pas dépasser la valeur maximale (le pire cas) atteinte par cette même quantité.

Exercice 10

Pourquoi le calcul naïf (double récursion sans mémoïsation) de Fibonacci a-t-il une complexité exponentielle, alors que la suite elle-même se calcule en O(n)O(n) avec une boucle ?

Corrigé

Le problème : redondance des calculs. L'appel récursif Fib(n)=Fib(n1)+Fib(n2)\text{Fib}(n)=\text{Fib}(n-1)+\text{Fib}(n-2) génère un arbre d'appels où chaque sous-problème Fib(k)\text{Fib}(k) pour k<nk<n est recalculé un grand nombre de fois — par exemple, Fib(n2)\text{Fib}(n-2) est appelé à la fois directement (par Fib(n)\text{Fib}(n)) et indirectement (via Fib(n1)Fib(n2)\text{Fib}(n-1)\to\text{Fib}(n-2)). Le nombre total d'appels suit la récurrence T(n)=T(n1)+T(n2)+O(1)T(n)=T(n-1)+T(n-2)+O(1), dont la solution est Θ(φn)\Theta(\varphi^n) (φ1,618\varphi\approx1{,}618) — une croissance exponentielle.

La solution : éviter la redondance. Une approche itérative (ou récursive avec mémoïsation) calcule chaque valeur Fib(k)\text{Fib}(k) une seule fois, en la stockant pour réutilisation ultérieure. En ne gardant en mémoire que les deux derniers termes calculés et en itérant de 00 à nn, on obtient un algorithme en O(n)O(n) — chaque valeur intermédiaire est calculée et utilisée immédiatement, sans recalcul.

C'est l'illustration la plus simple du principe de la programmation dynamique : transformer une récursion exponentielle redondante en un calcul polynomial en éliminant les recalculs de sous-problèmes identiques.

Exercice 11

Démontrer, en utilisant le théorème maître, que la recherche dichotomique dans un tableau trié de taille nn a une complexité Θ(logn)\Theta(\log n).

Corrigé

Mise en équation. À chaque étape de la dichotomie, on compare l'élément cherché à l'élément central du tableau (coût constant Θ(1)\Theta(1)), puis on poursuit la recherche dans une seule des deux moitiés (donc a=1a=1 sous-problème), de taille n/2n/2 (donc b=2b=2). La récurrence est :

T(n)=T(n/2)+Θ(1)T(n) = T(n/2) + \Theta(1)

Application du théorème maître. On identifie f(n)=Θ(nd)f(n)=\Theta(n^d) avec d=0d=0 (coût constant). On calcule p=logba=log21=0p=\log_b a=\log_2 1=0.

Comme d=0=pd=0=p, on est dans le cas d=pd=p du théorème maître, qui donne T(n)=Θ(nplogn)=Θ(n0logn)=Θ(logn)T(n)=\Theta(n^p\log n)=\Theta(n^0\log n)=\Theta(\log n).

Conclusion : la recherche dichotomique est Θ(logn)\Theta(\log n). \square C'est cohérent avec l'intuition : la taille de l'intervalle de recherche est divisée par 22 à chaque étape, donc il faut log2n\log_2 n étapes pour atteindre une taille 11.

Exercice 12

Un algorithme a la récurrence T(n)=4T(n/2)+Θ(n)T(n)=4T(n/2)+\Theta(n). Déterminer sa complexité via le théorème maître, et donner un exemple classique d'algorithme suivant cette récurrence.

Corrigé

Identification des paramètres. Dans T(n)=4T(n/2)+Θ(n)T(n)=4T(n/2)+\Theta(n) : a=4a=4 sous-problèmes, b=2b=2 (chacun de taille n/2n/2), et f(n)=Θ(n)=Θ(nd)f(n)=\Theta(n)=\Theta(n^d) avec d=1d=1.

Calcul de pp. p=logba=log24=2p=\log_b a=\log_2 4=2.

Comparaison dd vs pp. Comme d=1<p=2d=1<p=2, on est dans le cas d<pd<p du théorème maître, qui donne :

T(n)=Θ(np)=Θ(n2)T(n)=\Theta(n^p)=\Theta(n^2)

Exemple classique. Cette récurrence est typique de l'algorithme naïf de multiplication de matrices carrées par blocs récursifs : pour multiplier deux matrices n×nn\times n découpées en 4 blocs (n/2)×(n/2)(n/2)\times(n/2), la formule par blocs nécessite 88 multiplications récursives de matrices de taille n/2n/2 dans la version la plus naïve (et Θ(n2)\Theta(n^2) pour les additions de blocs) — donnant en fait T(n)=8T(n/2)+Θ(n2)T(n)=8T(n/2)+\Theta(n^2), soit Θ(n3)\Theta(n^3) (la complexité usuelle, non optimisée, de la multiplication matricielle). Une version simplifiée à 44 sous-appels correspondrait plutôt à un algorithme hypothétique de coût linéaire de combinaison, illustrant ici purement le mécanisme du théorème maître.

Exercice 13

Vrai ou faux : 2n=O(n100)2^n=O(n^{100}).

Corrigé

Faux. Toute fonction exponentielle 2n2^n croît, à terme, strictement plus vite que n'importe quel polynôme nkn^k (quel que soit kk fixé, même très grand comme 100100) : limn+2n/n100=+\lim_{n\to+\infty}2^n/n^{100}=+\infty. C'est un résultat classique d'analyse asymptotique (croissance exponentielle l'emporte toujours sur la croissance polynomiale).

Exercice 14

Démontrer que log(n!)=Θ(nlogn)\log(n!)=\Theta(n\log n) (formule utile pour analyser la complexité optimale du tri, via la formule de Stirling n!2πn(n/e)nn!\sim\sqrt{2\pi n}\,(n/e)^n).

Corrigé

Formule de Stirling. n!2πn(ne)nn!\sim\sqrt{2\pi n}\left(\dfrac{n}{e}\right)^n quand n+n\to+\infty, donc en prenant le logarithme :

ln(n!)=nlnnn+12ln(2πn)+o(1)\ln(n!) = n\ln n - n + \frac12\ln(2\pi n) + o(1)

Identification du terme dominant. Le terme nlnnn\ln n domine strictement les deux autres (n=O(n)=o(nlnn)-n=O(n)=o(n\ln n) et 12ln(2πn)=O(logn)=o(nlnn)\frac12\ln(2\pi n)=O(\log n)=o(n\ln n)), donc :

ln(n!)=nlnn+O(n)=Θ(nlnn)\ln(n!) = n\ln n + O(n) = \Theta(n\ln n)

Conclusion. Comme log\log et ln\ln diffèrent d'un facteur multiplicatif constant (log2x=lnx/ln2\log_2 x=\ln x/\ln2), on a aussi log(n!)=Θ(nlogn)\log(n!)=\Theta(n\log n). \square Ce résultat est la base de la preuve que tout algorithme de tri par comparaisons a une complexité Ω(nlogn)\Omega(n\log n) dans le pire cas (il y a n!n! permutations possibles à distinguer, donc il faut au moins log2(n!)=Θ(nlogn)\log_2(n!)=\Theta(n\log n) comparaisons pour les départager) — ce qui montre que le tri fusion, avec sa complexité Θ(nlogn)\Theta(n\log n), est optimal parmi les algorithmes de tri par comparaisons.

Exercice 15

On considère l'algorithme récursif de calcul de xnx^n par exponentiation rapide : xn=(xn/2)2x^n=(x^{n/2})^2 si nn pair, xn=x(x(n1)/2)2x^n=x\cdot(x^{(n-1)/2})^2 si nn impair. Établir sa complexité en nombre de multiplications.

Corrigé

Mise en équation. À chaque appel récursif, on réduit le problème de taille nn à un seul sous-problème de taille n/2n/2 (calculer xn/2x^{n/2} ou x(n1)/2x^{(n-1)/2}), suivi d'un nombre constant d'opérations supplémentaires (une mise au carré, et éventuellement une multiplication par xx si nn est impair). La récurrence est :

T(n)=T(n/2)+O(1)T(n) = T(n/2) + O(1)

Application du théorème maître. a=1a=1, b=2b=2, d=0d=0 (coût constant de combinaison). p=log21=0=dp=\log_2 1=0=d, donc on est dans le cas d=pd=p :

T(n)=Θ(n0logn)=Θ(logn)T(n) = \Theta(n^0\log n) = \Theta(\log n)

Comparaison avec la méthode naïve. Le calcul naïf de xnx^n par multiplications successives (x×x××xx\times x\times\cdots\times x, n1n-1 fois) coûte Θ(n)\Theta(n) multiplications. L'exponentiation rapide ne nécessite que Θ(logn)\Theta(\log n) multiplications — un gain exponentiel en pratique (par exemple, calculer x1000000x^{1\,000\,000} demande environ 2020 multiplications au lieu d'un million). Cette technique est à la base de nombreux algorithmes cryptographiques modernes (exponentiation modulaire RSA, etc.).

AlphaMath Académie · Analyse de la complexité algorithmique · Informatique L3 — Algorithmique, structures de données et graphes