Complexité algorithmique et Green IT
Ce que ce chapitre apporte
- Compter les opérations élémentaires d'un algorithme en fonction de la taille de l'entrée.
- Définir et utiliser les notations de Landau O, \Omega et \Theta.
- Situer les classes de complexité usuelles et comparer leurs croissances.
- Distinguer meilleur cas, pire cas et cas moyen, temps et espace.
- Distinguer la complexité d'un problème de celle d'un algorithme.
- Expliquer le fonctionnement d'une table de hachage et le coût de ses opérations.
- Confronter une complexité théorique à une mesure expérimentale.
- Relier optimisation algorithmique et sobriété énergétique.
Mesurer un algorithme, pas un programme
Chronométrer un programme donne un nombre qui dépend de la machine, du langage, de la version de l'interpréteur, de la charge du système et du jeu d'essai. Change n'importe lequel de ces facteurs, et le nombre change.
Opération élémentaire et fonction de coût
Une opération élémentaire est une opération dont le coût ne dépend pas de la taille de l'entrée : une comparaison, une affectation, une addition, un accès à une case de tableau.
La fonction de coût est le nombre d'opérations élémentaires effectuées sur une entrée de taille .
Compter, sur un exemple. Rechercher une valeur dans une liste non triée de éléments :
Dans le pire des cas (la valeur est absente) la boucle fait tours, avec par tour une comparaison d'indice, une comparaison de valeur et une incrémentation : environ.
3 dépend de ce qu'on décide d'appeler « une opération », et le + 2 devient négligeable dès que n dépasse quelques dizaines. Ce qui survit à tous ces choix arbitraires, c'est que T croît proportionnellement à n. C'est cette information-là qu'on garde, et rien d'autre.
La notation de Landau
On dit que est dominée par , et on note , s'il existe un rang et un réel tels que :
Trois notations, et il faut savoir laquelle on emploie :
- : majoration : « ne croît pas plus vite que ». C'est la plus utilisée, parce qu'on veut garantir un pire cas.
- : minoration : « croît au moins aussi vite que ». C'est celle des bornes inférieures, donc des théorèmes d'impossibilité.
- : encadrement : et à la fois. C'est la plus précise, et souvent celle qu'on veut vraiment dire.
Θ(n) est aussi, formellement, en O(n²), et en O(n¹⁰⁰). La majoration reste vraie, elle est simplement mauvaise.Quand tu affirmes une complexité, dis si c'est une majoration ou un encadrement. Écrire
Θ quand tu le sais est plus fort, plus précis, et signale que tu as compris la différence.
Montrons que .
Prenons et . Pour tout on a , donc .
Donc : la définition est vérifiée avec et . ∎
Le couple n'est pas unique : n'importe quel conviendrait. Exhiber un couple suffit, c'est tout ce que demande la définition.
Les classes usuelles
| Complexité | Nom | Exemple typique | ||
|---|---|---|---|---|
| constante | accès à une case, à une clé de dictionnaire | 1 | 1 | |
| logarithmique | recherche dichotomique | 10 | 20 | |
| linéaire | parcours d'une liste | 10³ | 10⁶ | |
| quasi-linéaire | tri par fusion, tri rapide | 10⁴ | 2 × 10⁷ | |
| quadratique | double boucle imbriquée | 10⁶ | 10¹² | |
| exponentielle | énumération de tous les sous-ensembles | 10³⁰¹ | inatteignable |
O(n²) traite un million d'éléments en environ 11 jours. Le même problème en O(n log n) demande 20 millisecondes. Ce n'est pas une optimisation, c'est la différence entre faisable et infaisable.Et
O(2ⁿ) avec n = 100 dépasse le nombre d'atomes de l'univers observable : aucune machine, jamais, ne le fera.
O(n) : il double. O(n²) : il quadruple. O(log n) : il augmente d'une constante. O(2ⁿ) : il est élevé au carré.C'est aussi la façon la plus simple de deviner la complexité d'un code inconnu : mesure sur
n, puis sur 2n, et regarde le rapport.
Meilleur cas, pire cas, cas moyen
Pour une même taille , le nombre d'opérations dépend souvent du contenu de l'entrée. La recherche linéaire trouve parfois au premier essai, parfois au dernier.
- Meilleur cas : ici. Rarement intéressant : il décrit un coup de chance.
- Pire cas : . C'est la garantie, donc ce qu'on annonce par défaut.
- Cas moyen : . Le plus réaliste, mais il exige une hypothèse sur la distribution des entrées, qu'il faut alors écrire.
O(n²) : sur une liste déjà triée avec un pivot mal choisi. Son cas moyen est Θ(n log n), et c'est celui qu'on observe en pratique. C'est pourquoi il reste le tri le plus utilisé malgré un pire cas médiocre : annoncer le seul pire cas donnerait ici une image fausse de son comportement réel.
Complexité spatiale
La complexité spatiale est la quantité de mémoire supplémentaire utilisée par l'algorithme, en fonction de , sans compter l'entrée elle-même.
Elle s'exprime avec les mêmes notations, et elle s'échange souvent contre du temps : c'est exactement ce que fera la table de hachage plus bas, en consommant de mémoire pour ramener une recherche de à .
n occupe O(n) d'espace même si elle ne déclare aucune variable, et c'est ce qui provoque le RecursionError.
Complexité d'un problème, complexité d'un algorithme
C'est la confusion la plus fréquente, et elle survit longtemps parce qu'elle ne se voit pas sur de petits jeux de données.
La complexité d'un algorithme est le coût de cet algorithme-là. Elle se calcule en le lisant.
La complexité d'un problème est le coût du meilleur algorithme possible pour le résoudre, y compris ceux que personne n'a encore trouvés. C'est une borne inférieure, et elle se démontre.
O(n²) par force brute, O(n log n) par tri et deux pointeurs, O(n) par table de hachage.La complexité du problème, elle, est
Θ(n) : on ne peut pas faire mieux que lire l'entrée une fois, et l'algorithme par hachage atteint cette borne. Le problème est donc résolu de façon optimale, chercher un algorithme plus rapide reviendrait à espérer ne pas lire toutes les données.
Les tables de hachage
Une table de hachage associe des clés à des valeurs. Une fonction de hachage transforme une clé en un indice de tableau ; on range la valeur à cet indice. Insertion, recherche et suppression coûtent alors en moyenne.
Le principe est celui du vestiaire : plutôt que de parcourir tous les manteaux pour trouver le sien, on calcule un numéro à partir du ticket et on va directement au bon casier.
Les collisions
Deux clés différentes peuvent donner le même indice : c'est une collision. Elles sont inévitables, il y a plus de clés possibles que de cases. Deux stratégies pour les traiter : le chaînage, où chaque case contient la liste des paires qui s'y trouvent, et l'adressage ouvert, où l'on cherche la case libre suivante.
O(n). C'est le pire cas, et il est réellement exploitable : une attaque par collisions de hachage consiste à envoyer à un serveur des clés choisies pour se percuter, jusqu'à l'effondrement.En pratique, avec une bonne fonction de hachage et un taux de remplissage maîtrisé, le coût amorti reste
O(1). Mais on écrit « O(1) en moyenne », jamais « O(1) » tout court.
En Python, la table de hachage est le type dict, et l'appartenance à un set en est un cas particulier.
Et une fonction de hachage écrite à la main, pour voir d'où viennent les collisions :
hash() de Python donne un résultat différent à chaque exécutionhash("clavier") : la valeur change. Ce n'est pas un bug, c'est la protection contre l'attaque par collisions décrite plus haut, une graine aléatoire est tirée au démarrage, ce qui empêche un attaquant de préparer à l'avance des clés qui se percutent. C'est aussi la raison pour laquelle un hash() ne doit jamais être stocké sur disque ni transmis : il n'a de sens que dans le processus qui l'a calculé.
1.Un algorithme en O(n²) est-il toujours plus lent qu'un O(n log n) ?
2.Une table de hachage donne un accès en O(1)…
3.Réduire la complexité d'un traitement a un effet Green IT parce que…
Le 2-sum, trois algorithmes
Le problème : étant donné une liste d'entiers et une cible, existe-t-il deux éléments dont la somme vaut la cible ? Les valeurs peuvent être négatives, il peut y avoir des doublons, et rien n'est trié.
Force brute : en temps, en espace
On essaie toutes les paires. Il y en a , ce qui donne bien un comportement quadratique.
Tri puis deux pointeurs : en temps
On trie, puis on place un pointeur à chaque extrémité. Si la somme est trop grande, on recule celui de droite ; trop petite, on avance celui de gauche. Le tri domine le coût total.
Table de hachage : en temps, en espace
Un seul parcours. Pour chaque élément , on regarde si son complément cible − x a déjà été rencontré. La table rend cette question , et c'est tout le gain.
Théorie contre mesure
La théorie prédit des rapports, pas des durées. Confrontons-la à l'expérience : on mesure sur des tailles croissantes, dans le pire cas (aucune solution), et on trace en échelle logarithmique, où une complexité en nᵃ devient une droite de pente .
n² donne une droite de pente 2, un temps en n une pente 1, et n log n une pente légèrement supérieure à 1. La pente mesurée est donc une estimation expérimentale de l'exposant : c'est la façon de vérifier une complexité annoncée sans lire le code.Attends-toi à des écarts sur les petites tailles, les constantes, le cache du processeur et le ramasse-miettes y pèsent plus que l'asymptotique. C'est normal, et c'est exactement ce que veut dire « asymptotique ».
Green IT
Le Green IT désigne l'ensemble des démarches visant à réduire l'empreinte environnementale du numérique : fabrication des équipements, consommation d'énergie en exploitation, et fin de vie.
Un point de repère souvent rappelé : la fabrication des terminaux pèse une part majoritaire de l'empreinte du numérique, devant la consommation électrique d'usage. Les chiffres précis varient selon le périmètre retenu par chaque étude, et il faut les citer comme des estimations ; l'ordre de grandeur, lui, est stable d'une source à l'autre.
O(n²) à O(n) sur un million d'enregistrements ne divise pas la facture par deux : il la divise par un facteur de l'ordre du million. Aucune action sur le refroidissement d'un centre de données n'atteint ce rapport.Corollaire moins confortable : allonger la durée de vie du matériel pèse souvent davantage que le code, puisque la fabrication domine. Un algorithme efficace permet de garder un serveur plus longtemps, c'est par là que l'optimisation rejoint l'essentiel.
Quelques pratiques, du plus au moins efficace :
- Choisir le bon algorithme et la bonne structure de données. C'est le levier au meilleur rapport effort/gain, et de loin.
- Ne pas calculer deux fois la même chose : mise en cache, mémoïsation, calculs incrémentaux.
- Déplacer les traitements lourds vers les heures où l'électricité est la moins carbonée, ce que permet précisément un réseau intelligent, qui rend la production visible en temps réel.
- Dimensionner au besoin réel : un serveur surdimensionné consomme même à vide.
- Allonger la durée de vie du matériel, ce que rend possible un logiciel qui n'exige pas toujours plus.
C'est ce qui donne son sens au problème du 2-sum : apparier des surplus de production avec des demandes suppose de traiter en continu des centaines de milliers de relevés. Un algorithme quadratique interdirait le temps réel, et donc l'arbitrage énergétique lui-même.
Exercices type
Donner la complexité de deux boucles imbriquées où la seconde va de $i$ à $n$
Le nombre d'itérations vaut .
C'est un polynôme de degré 2, donc .
Le point à retenir : imbriquer à partir de ne change pas la classe. On économise un facteur 2 par rapport à la double boucle complète, et ce facteur 2 disparaît dans la notation.
Un algorithme traite 1 000 éléments en 1 seconde. Combien pour 10 000, en $O(n)$, $O(n \log n)$ et $O(n^2)$ ?
: facteur 10 → 10 secondes.
: facteur → 13 secondes.
: facteur 100 → 100 secondes.
Refais le calcul pour 1 000 000 d'éléments : donne 1 000 s (17 minutes), donne 10⁶ s, soit 11 jours et demi. L'écart ne cesse de croître, et c'est précisément ce que dit l'asymptotique.
Prouver que $T(n) = 5n^2 + 3n + 7 = O(n^2)$
Pour tout on a et , donc :
La définition est vérifiée avec et . ∎
On peut même dire mieux : , puisque pour tout , ce qui fournit la minoration avec .
Pourquoi la recherche dans une table de hachage n'est-elle pas garantie en $O(1)$ ?
À cause des collisions. Si plusieurs clés produisent le même indice, elles s'accumulent dans la même case, et il faut les parcourir. Dans le pire cas (toutes les clés en collision) la table dégénère en liste et la recherche devient .
En pratique, avec une fonction de hachage qui répartit bien et un taux de remplissage tenu sous contrôle par redimensionnement, le coût amorti reste .
Ce pire cas n'est pas qu'une hypothèse d'école : c'est un vecteur d'attaque par déni de service, où l'attaquant fabrique des clés choisies pour se percuter. Les langages modernes s'en protègent en introduisant une graine aléatoire dans la fonction de hachage.
Le tri prend $O(n \log n)$, la recherche de la paire $O(n)$. Quelle est la complexité totale ?
.
On additionne les étapes : . Le terme dominant l'emporte, puisque n log n croît plus vite que .
C'est la règle générale d'un enchaînement séquentiel : la complexité totale est celle de l'étape la plus coûteuse. Optimiser la recherche de la paire ne servirait donc à rien tant que le tri est là, et c'est ce qui justifie de chercher une méthode qui évite complètement de trier.
Pourquoi mesure-t-on des temps qui ne correspondent pas exactement à la théorie sur de petites tailles ?
Parce que l'analyse asymptotique décrit le comportement quand tend vers l'infini, et néglige tout le reste.
Sur de petites tailles, ce « reste » domine : les constantes multiplicatives, les termes d'ordre inférieur, le cache du processeur (une petite structure y tient entièrement, une grande non), le ramasse-miettes, et le bruit de mesure du système.
Conséquence pratique, souvent contre-intuitive : un algorithme en peut être plus rapide qu'un sur de petites entrées, ses constantes étant plus faibles. C'est pourquoi les tris industriels basculent sur un tri par insertion en dessous d'une quinzaine d'éléments.
La méthode
- Nomme en toute première ligne. « = nombre de relevés », sans cela, la complexité ne veut rien dire.
- Compte les opérations élémentaires, boucle par boucle, en identifiant celle qui domine.
- Précise le cas : pire, moyen ou meilleur. Par défaut, le pire.
- Écris pour une majoration, quand tu as l'encadrement.
- Prouve quand on te le demande : exhibe un et un , c'est tout ce qu'exige la définition.
- Donne aussi la complexité spatiale. Un gain de temps payé en mémoire est un compromis, pas un progrès automatique.
- Confronte à la mesure : trace en log-log, lis la pente, compare à l'exposant théorique.
- Conclus en énergie quand le sujet est le Green IT : un facteur de complexité est un facteur sur la consommation.
En résumé
- On mesure comment le temps croît avec , pas combien de secondes il prend.
- Opération élémentaire : coût indépendant de . Fonction de coût : leur nombre.
- s'il existe et tels que .
- majore, minore, encadre. est plus fort, et plus précis.
- Classes usuelles : .
- Réflexe du doublement : double, quadruple, est élevé au carré.
- Pire cas par défaut ; le cas moyen exige une hypothèse sur les entrées.
- Complexité spatiale : mémoire supplémentaire, pile d'appels comprise.
- La complexité d'un problème est une borne inférieure ; celle d'un algorithme se lit dans le code.
- Table de hachage : en moyenne, en cas de collisions massives.
- 2-sum : brute, par tri, par hachage, qui atteint l'optimum du problème.
- Dans un enchaînement, l'étape la plus coûteuse fixe le total.
- En log-log, la pente estime l'exposant réel.
- Green IT : le bon algorithme est le premier levier de sobriété, et il prolonge la vie du matériel.
Et ensuite ? Le 2-sum se contentait d'une liste de nombres. Le dernier problème du bloc porte sur des données qui ont une structure de relations, quelles tâches se chevauchent, quels serveurs peuvent les partager. C'est l'objet de la théorie des graphes.