Module 0 · Les fondations

L'analyse asymptotique

Tout le cours repose sur une seule phrase, et elle est étrange la première fois qu'on la lit : on mesure la vitesse d'un algorithme sans jamais le faire tourner. Pas de chronomètre, pas de machine, pas même de langage — on compte, et on regarde ce que ce compte devient quand l'entrée grossit.

💡 Le concept

Un algorithme n'a pas de vitesse. Il a une loi de croissance — une réponse à la question « si je double l'entrée, qu'arrive-t-il au travail à faire ? ». Cette loi ne dépend ni du processeur, ni du compilateur, ni de l'humeur du système d'exploitation. C'est pour ça qu'on peut la démontrer sur papier, et c'est pour ça qu'un examen peut te la demander.

Toute la difficulté du module est là : accepter qu'on jette délibérément de l'information — les constantes, les petites valeurs, le détail des opérations — pour garder la seule chose qui survit au changement de machine.

Pourquoi le chronomètre ment

Commençons par la scène qui rend tout le reste nécessaire. Tu écris deux versions d'un programme qui trie une liste. Tu les lances sur ta machine, avec mille nombres : les deux répondent en moins d'une seconde. Laquelle est la meilleure ?

La question paraît simple et elle est piégée, parce que le chronomètre ne mesure pas ce que tu crois. Il mesure ton algorithme plus la fréquence de ton processeur, plus la quantité de mémoire libre, plus les optimisations que ton compilateur a bien voulu appliquer, plus ce que le système d'exploitation faisait pendant ce temps, plus l'entrée précise que tu as choisie. Change n'importe lequel de ces six termes et le verdict change. Deux personnes qui mesurent le même algorithme n'obtiennent pas le même résultat, et aucune des deux n'a tort.

Il faut donc une mesure qui ne dépende que de l'algorithme. On en connaît une : le nombre d'opérations élémentaires — comparaisons, affectations, additions — qu'il effectue en fonction de la taille de son entrée. On note cette quantité T(n), où n est la taille de l'entrée. Elle se compte à la main, sur le papier, sans allumer quoi que ce soit.

🧭 L'analogie qui tient tout le module

Deux voitures démarrent côte à côte. L'une plafonne à 50 km/h, l'autre monte à 250. Sur cent mètres, tu ne verras aucune différence — les deux partent de zéro et n'ont pas le temps d'exprimer quoi que ce soit. Sur l'autoroute, l'écart devient une évidence.

L'analyse d'algorithmes ne s'intéresse pas au cent mètres. Elle étudie le comportement quand le problème devient gros, parce que c'est là, et seulement là, que la différence entre deux algorithmes cesse d'être une question de matériel.

Reste à se convaincre que cet écart n'est pas théorique. Prenons deux tris, l'un en n² opérations, l'autre en n log n, et supposons qu'une opération coûte une microseconde :

nn² — tempsn log n — temps
10 000100 s0,13 s
100 000≈ 2,8 h1,7 s
1 000 000≈ 11,5 jours≈ 20 s

Sur un million d'éléments, l'un rend sa réponse pendant que tu prends un café, l'autre pendant que tu prends des vacances. Et voici ce qu'il faut vraiment retenir de ce tableau : un superordinateur cent fois plus rapide ne sauve pas le premier. Onze jours divisés par cent font encore près de trois heures. Le choix de l'algorithme bat le choix de la machine, et il le bat de si loin que la comparaison n'a même pas de sens.

Une dernière précision de vocabulaire, parce qu'elle revient à chaque ligne du cours. T(n) dépend encore de détails sans intérêt — le nombre exact d'affectations, la façon dont on a écrit la condition. Ce qui nous intéresse est son ordre de croissance : la forme que prend T(n) quand n grandit, une fois les constantes mises de côté. Tout le reste du module consiste à donner un langage précis à cette idée floue.

L'opération baromètre

Compter toutes les opérations d'un algorithme est fastidieux, et — c'est le point important — parfaitement inutile. Regarde une boucle : si une instruction du corps s'exécute n² fois, ses voisines aussi. Elles montent et descendent ensemble. En compter une seule suffit donc à connaître l'ordre de croissance de toutes.

🧭 Le chronomètre du cuisinier

Un cuisinier doit préparer cent carottes. Pour chacune : la prendre, l'éplucher, la couper, la mettre dans le bol. Pour estimer le temps total, personne ne chronomètre les quatre gestes. On en chronomètre un — éplucher — et on multiplie par cent. L'estimation est bonne, parce que les quatre gestes se répètent le même nombre de fois.

C'est exactement l'idée de l'opération baromètre : une opération de l'algorithme exécutée au moins aussi souvent que toutes les autres. Compter ses exécutions suffit à déterminer l'ordre de croissance de T(n). Le nom est bien choisi — un baromètre ne mesure pas le temps qu'il fait, il en donne une indication fidèle.

Comment le choisir ? Trois réflexes, dans cet ordre. L'instruction la plus profondément imbriquée dans les boucles est presque toujours le bon candidat. Si plusieurs se présentent, on garde celle qui s'exécute le plus souvent — les autres seront dominées de toute façon. Et pour un algorithme de tri ou de recherche, c'est typiquement la comparaison ou l'affectation.

Le code ci-dessous n'est là que pour ça : montrer où tombe le baromètre dans le cas le plus fréquent du cours, deux boucles imbriquées. Lis les commentaires avant le code — ce sont eux qui portent le raisonnement.

somme_matrice.cpp
int somme = 0;

for (int i = 0; i < n; i++) {          // la boucle externe tourne n fois
  for (int j = 0; j < n; j++) {        // et pour CHACUN de ces n tours, n fois encore
    somme += tableau[i][j];            // ← le BAROMÈTRE : n × n exécutions
  }
}

Les comparaisons i < n et j < n sont elles aussi en Θ(n²) — on n'annonce qu'une seule opération baromètre, la plus interne.

Vérifie-le à la main, c'est le seul moyen d'y croire vraiment. Pour n = 3, les couples (i, j) parcourus sont (0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2). Le baromètre s'exécute neuf fois, soit 3 × 3. Cette vérification sur une petite valeur est le réflexe le plus rentable de tout le cours : elle attrape la moitié des erreurs de comptage.

⚠️ Piège fréquent

Le baromètre choisi trop bas. Si tu prends comme baromètre une opération exécutée moins souvent qu'une autre du même code, tu sous-estimes la complexité — et rien dans ta démonstration ne te le dira, parce que le calcul reste juste, seulement il porte sur la mauvaise opération.

Le réflexe de diagnostic tient en une question, à te poser avant de commencer à compter : y a-t-il, quelque part dans ce code, une instruction exécutée plus souvent que celle que j'ai choisie ? Si oui, c'est elle, ton vrai baromètre.

🧠 Quiz éclair

Quelle est l'opération baromètre ici — et le résultat asymptotique change-t-il selon celle qu'on retient ?

for (int i = 0; i < n; i++) {
  std::cout << "Bonjour ";        // (a)
  for (int j = 0; j < 10; j++)
    std::cout << j;               // (b)
}

Le baromètre formel est (b) : il s'exécute 10n fois, contre n fois pour (a). Mais la conclusion est la même dans les deux cas — 10 est une constante, donc 10n et n sont tous deux en Θ(n).

La leçon est que la borne intérieure d'une boucle compte : c'est j < 10 et non j < n. Une boucle bornée par une constante ne change pas l'ordre de croissance, elle ne fait que multiplier le compte.

Pire cas, meilleur cas, cas moyen

Un même algorithme, sur deux entrées de même taille, peut se comporter très différemment. Cherche une valeur dans une liste de mille entiers, en les parcourant un par un. Si elle est en première position, une comparaison suffit. Si elle est en dernière — ou absente —, il en faut mille. Si elle est au milieu, environ cinq cents.

Trois nombres pour un seul algorithme et une seule taille d'entrée. Lequel annonce-t-on ? La réponse honnête est : cela dépend de ce qu'on veut garantir. D'où trois mesures distinctes, et pas une hiérarchie entre elles.

Le casCe qu'il mesureQuand on l'emploie
Pire le maximum d'opérations parmi toutes les entrées de taille n quand il faut une garantie : temps réel, médical, embarqué
Meilleur le minimum d'opérations parmi ces mêmes entrées rarement seul ; sert à montrer qu'on ne peut pas faire plus vite
Moyen l'espérance, sur une distribution supposée des entrées quand un ralentissement occasionnel est acceptable

Sur la recherche linéaire, cela donne : meilleur cas Θ(1) — la valeur est en tête ; pire cas Θ(n) — elle est en queue ou absente ; cas moyen Θ(n) — si elle est présente et uniformément répartie, on s'arrête en moyenne à la position (n + 1)/2, donc après environ n/2 comparaisons. Le meilleur cas est spectaculaire et parfaitement trompeur : en pratique, cet algorithme est linéaire, parce que deux de ses trois mesures le sont.

Le choix entre pire et moyen n'est pas mathématique, il est contextuel — et c'est précisément ce que l'examen aime demander. Un détecteur de fuite de gaz doit déclencher son alarme avant un délai fixé dans tous les cas : on exige une garantie de pire cas, même si le cas moyen est dix fois meilleur. Un tri sur une page web peut au contraire accepter un excellent cas moyen au prix d'un pire cas occasionnellement lent : personne ne remarquera la fois sur mille.

🧠 Quiz éclair

L'algorithme A a un meilleur pire cas que B, mais B est meilleur en moyenne. Lequel choisir ?

Il n'y a pas de réponse universelle, et c'est ça, la réponse attendue. Si une garantie dure est obligatoire — l'alarme qui doit sonner à temps —, A. Si un ralentissement occasionnel passe inaperçu, B, qui sera plus rapide la quasi-totalité du temps.

Le tri rapide en est l'illustration classique : pire cas en Θ(n²), cas moyen en Θ(n log n). Il reste omniprésent parce que son pire cas est rare avec un bon pivot et que ses constantes sont excellentes. Là où il faut une garantie, on prend le tri par fusion, dont le pire cas est en Θ(n log n).

O, Ω, Θ : le plafond et le plancher

Tu sais maintenant compter, et tu obtiens des choses comme T(n) = 3n² + 5n + 17. C'est précis ; c'est trop précis. Les trois constantes viennent de la machine et de la façon dont tu as écrit le code — elles ne disent rien sur l'algorithme. Ce qui reste vrai partout, c'est « ça grandit comme n² ». Les trois notations asymptotiques existent pour dire exactement cela, et rien de plus.

🧭 Trois façons de parler d'une vitesse

Sur une autoroute : « tu roules à au plus 130 » est un plafond — tu peux rouler moins vite, jamais plus. « Tu roules à au moins 60 » est un plancher. « Tu roules entre 60 et 130 » est un encadrement, et c'est la seule des trois phrases qui te situe vraiment.

O est le plafond, Ω le plancher, Θ l'encadrement. Retiens la forme des lettres si ça aide : le O est ouvert vers le haut de l'alphabet, l'Ω est posé sur ses deux pieds.

Avant les formules, l'intuition en français. Dire que f appartient à O(g), c'est dire qu'à partir d'une certaine taille, f reste sous un multiple constant de g. Ω renverse l'inégalité : f reste au-dessus. Θ enferme f entre deux multiples de g. Le membre de phrase « à partir d'une certaine taille » n'est pas une précaution de style : c'est lui qui autorise à ignorer les petites valeurs de n, et c'est le mot asymptotique qui le porte.

Voici les trois définitions telles que l'examen les demande. Elles se ressemblent beaucoup, et c'est volontaire — seul le sens de l'inégalité change.

O (grand O) — la borne supérieure. On dit que f(n) appartient à O(g(n)) s'il existe deux constantes positives c et n0 telles que :

f(n) c·g(n) , nn0

La virgule se lit « pour tout » : l'inégalité n'est exigée qu'à partir du seuil, jamais avant.

Ω (grand oméga) — la borne inférieure. Mêmes constantes, même seuil, et une seule chose change — le sens de l'inégalité :

f(n) c·g(n) , nn0

Θ (grand thêta) — l'encadrement. Il faut cette fois trois constantes positives — deux multiplicateurs et toujours un seuil — telles que, à partir de ce seuil :

c1·g(n) f(n) c2·g(n)

Décortiquons, parce que ces quatre symboles reviendront à chaque démonstration du cours. f est ta fonction réelle, celle que tu as comptée — 3n² + 5n + 17. g est la fonction simple à laquelle tu la compares — n². La constante c est le droit d'agrandir g autant qu'il faut : c'est elle qui matérialise « on ignore les constantes ». Et n0 est un seuil : en dessous, l'inégalité a le droit d'être fausse. L'inégalité complète dit donc, littéralement : passé un certain point, f reste sous une copie agrandie de g.

Un exemple complet, du genre qu'on demande de rédiger. Montrons que 3n² + 5n + 17 est dans O(n²). Il faut exhiber un c et un n0. Pour n ≥ 4, on a 5n + 17 qui reste sous n² ; donc 3n² + 5n + 17 reste sous 3n² + n² = 4n². Le couple c = 4, n0 = 4 convient. On n'a pas à trouver le meilleur couple — n'importe lequel qui marche suffit à établir l'appartenance.

Enfin, la relation entre les trois, qui explique pourquoi Θ est la mesure qu'on préfère quand on la connaît : f est dans Θ(g) si et seulement si elle est à la fois dans O(g) et dans Ω(g). Θ dit « au plus » et « au moins » dans la même phrase.

⚠️ Piège fréquent

O n'est pas Θ. Dire « cet algorithme est en O(n²) » ne veut pas dire qu'il coûte environ n². Un algorithme en Θ(n) est aussi dans O(n²), dans O(n³) et dans O(2ⁿ) — toutes ces bornes sont vraies, aucune n'est serrée. La notation O ne promet qu'un plafond.

Le symptôme, dans une copie d'examen : on conclut qu'un algorithme en O(n³) est plus lent qu'un algorithme en O(n²). C'est un raisonnement invalide — les deux pourraient être en Θ(1). Le réflexe : dès qu'une question compare deux algorithmes, regarde si les bornes données sont des O ou des Θ. Avec des O, la seule réponse honnête est souvent « on ne peut pas conclure ».

✍️ Exercice de lecture

A est en O(n²), B est en O(n³). Est-il possible que B soit asymptotiquement plus efficace que A ? Et si on remplace les deux O par des Θ ?

Voir le corrigé

Avec O : oui. Il suffit d'un contre-exemple. Prends fA(n) = n² et fB(n) = n. La première est bien dans O(n²), la seconde est bien dans O(n³) — sa borne n'est pas serrée, mais l'énoncé n'exigeait pas qu'elle le soit. Et pourtant B est plus rapide.

Avec Θ : non. Θ encadre par le haut et par le bas. Si A est en Θ(n²), sa croissance est celle de n², à des constantes près ; si B est en Θ(n³), la sienne est celle de n³. Comme n² croît strictement moins vite que n³, A l'emporte nécessairement.

La morale, et c'est elle qu'on attend : Θ porte strictement plus d'information que O. Quand tu connais la borne exacte, annonce-la en Θ.

Il reste une question pratique : exhiber c et n0 à la main est parfois pénible. Il existe un raccourci, et le cours l'accepte comme démonstration : étudier la limite du rapport des deux fonctions.

L= lim n f(n) g(n)
Si L vaut…alors…ce que ça veut dire
0 f ∈ O(g), mais pas Θ(g) f est négligeable devant g : elle croît strictement moins vite
entre 0 et ∞ f ∈ Θ(g) les deux croissent au même rythme, à un facteur constant près
f ∈ Ω(g), mais pas Θ(g) f explose devant g : elle croît strictement plus vite

Appliqué à 3n² + 5n contre n², le rapport vaut 3 + 5/n, qui tend vers 3. Comme 3 est strictement entre 0 et l'infini, la conclusion tombe : 3n² + 5n est dans Θ(n²). Trois lignes, aucune constante à deviner.

La hiérarchie des ordres de croissance

Une poignée de fonctions revient sans cesse, et il faut les connaître dans l'ordre, comme on connaît l'ordre des opérations. Du plus rapide au plus lent : constante, logarithmique, racine, linéaire, linéarithmique, quadratique, cubique, exponentielle, factorielle.

1 logn n n nlogn nlogn n2 n3 2n n!

La chaîne est coupée en deux lignes qui se recouvrent sur n log n : une formule ne se coupe pas toute seule, et celle-ci ne tiendrait pas sur la largeur d'un téléphone. Le symbole d'inclusion se lit ici « est contenu dans, mais ne remplit pas ». Θ(log n) ⊂ Θ(n) signifie : toute fonction logarithmique est dans O(n), et la réciproque est fausse.

Cette chaîne est facile à réciter et difficile à sentir. Le tableau suivant est là pour ça — mêmes fonctions, une microseconde par opération, et des tailles d'entrée réalistes.

nlog₂ nnn log₂ n2ⁿ
103 µs10 µs33 µs100 µs1 ms
1007 µs100 µs664 µs10 ms≈ 4 × 10¹⁶ ans
1 00010 µs1 ms10 ms1 shors d'atteinte
1 000 00020 µs1 s20 s≈ 11,5 jourshors d'atteinte

Deux choses à emporter de ce tableau. D'abord, le logarithme est presque une constante : quand n passe de mille à un million, il passe de dix à vingt. Doubler l'entrée n'ajoute qu'une unité. C'est pour cette raison que tout le cours cherchera à transformer des parcours linéaires en descentes d'arbre.

Ensuite, l'exponentielle n'est pas « lente », elle est impossible. À cent éléments, un algorithme en 2ⁿ demande environ quatre cent mille milliards de milliards d'années — trois millions de fois l'âge de l'univers. Le seuil pratique se situe autour de trente : au-delà, il ne s'agit plus d'attendre, il s'agit de changer d'algorithme.

Analyser une boucle en quatre gestes

On arrive à l'exercice le plus fréquent du cours et de l'intra : on te donne une boucle, tu rends un Θ. La bonne nouvelle est qu'il existe une recette, toujours la même, et que les corrigés du cours la suivent à la lettre.

Elle tient en quatre gestes, à faire dans l'ordre. Un : identifier l'opération baromètre — presque toujours l'instruction la plus interne. Deux : compter ses exécutions en fonction de n, en l'écrivant comme une somme. Trois : simplifier cette somme à l'aide du formulaire. Quatre : conclure en notation asymptotique.

📖 La formule

Les quatre gestes de l'analyse

// GESTE 1 — le baromètre : l'instruction la plus interne.
// GESTE 2 — le compte : pour i fixé, l'interne tourne i fois.
//           Donc le total s'écrit  somme de i, pour i de 1 à n.
// GESTE 3 — le formulaire : cette somme vaut n(n+1)/2.
// GESTE 4 — la conclusion : n(n+1)/2 est dominé par n², donc Theta(n^2).

for (int i = 1; i <= n; i++) {
  for (int j = 1; j <= i; j++) {
    somme += j;                    // ← le baromètre
  }
}

On l'emploie sur toute boucle imbriquée ; on la reconnaît au fait que la borne de la boucle interne dépend de l'indice externe, ce qui donne toujours une somme et non un produit.

Avant de compter quoi que ce soit, il y a un réflexe qui répond à lui seul à la moitié des questions : regarder comment l'indice avance. Un incrément additif — i++, i += 2 — donne une boucle linéaire. Un incrément multiplicatif — i *= 2, i /= 2 — donne une boucle logarithmique. C'est la première chose à observer, et c'est presque toujours suffisant pour savoir où l'on va.

La raison se voit mieux qu'elle ne s'explique. La figure ci-dessous met côte à côte les valeurs que chacune des deux boucles visite avant d'atteindre le bout.

Les valeurs que la boucle visite, selon son incrément
i = i + 1 1 2 3 4 5 6 7 8 9 10 11 12
i = i * 2 1 2 4 8
i de 1 à 4 i de 5 à 8 i de 9 à 12
Une colonne vaut une valeur de i. La boucle additive se pose sur chaque valeur avant d'atteindre le bout de la rangée ; la boucle multiplicative ne se pose que sur les puissances de deux, et saute tout le reste. C'est toute la distance entre Θ(n) et Θ(log n) — et elle se creuse d'autant plus que la rangée s'allonge.

Le raisonnement formel derrière la figure tient en une ligne. Si i part de 1 et double à chaque tour, alors après k tours il vaut 2k. La boucle s'arrête quand 2k atteint n, c'est-à-dire quand k atteint log₂ n. Le baromètre s'exécute donc environ log₂ n fois.

Voici les patrons qui couvrent la quasi-totalité des questions d'examen. Vaut mieux les reconnaître que les recalculer.

Le patronItérationsOrdre
incrément additif jusqu'à nn, ou n/2, ou n/3…Θ(n)
incrément multiplicatif jusqu'à nlog₂ nΘ(log n)
deux boucles imbriquées en nΘ(n²)
interne bornée par i (somme triangulaire)n(n+1)/2Θ(n²)
externe en n × interne en log ilog(n!)Θ(n log n)
externe en log n × interne en i2n − 1Θ(n)

Le geste 4 — conclure — s'appuie sur trois règles qu'il vaut mieux connaître par leur nom, parce que les énoncés d'examen les nomment.

La règle du maximum. Deux morceaux de programme qui se suivent coûtent l'ordre du plus cher des deux : f + g est dans Θ(max(f, g)). Un programme en n² suivi d'un programme en n log n est en Θ(n²) — le second est absorbé. C'est ce qui autorise à jeter tous les termes sauf le dominant, et c'est aussi pourquoi optimiser la partie rapide d'un programme lent ne sert à rien.

La règle multiplicative. Deux boucles imbriquées dont les bornes sont indépendantes coûtent le produit : une externe en f et une interne en g donnent Θ(f·g). Le mot important est indépendantes — dès que la borne interne dépend de l'indice externe, ce n'est plus un produit mais une somme, et c'est tout l'objet de la section suivante.

La transitivité. Si f est dans O(g) et g dans O(h), alors f est dans O(h). Elle paraît anodine et sert tout le temps : c'est elle qui permet d'enchaîner les majorations sans repartir de la définition à chaque étape.

Un cas typique d'examen, qui les combine. Un programme fait une préparation en O(n), puis une boucle de n tours dont le corps est en O(log n). La boucle coûte O(n log n) par la règle multiplicative ; le total est O(n) + O(n log n), que la règle du maximum ramène à O(n log n). La préparation a disparu, et c'est correct.

⚠️ Piège fréquent

Confondre i = i + 2 et i = i * 2. Les deux lignes se ressemblent à l'œil, à un caractère près, et donnent des ordres différents — Θ(n) pour la première, Θ(log n) pour la seconde. C'est la faute la plus coûteuse de l'exercice, parce qu'elle ne se rattrape jamais : tout le calcul qui suit est correct et faux.

Le réflexe de diagnostic : avant d'écrire quoi que ce soit, entoure l'opérateur de mise à jour de l'indice et demande-toi s'il ajoute ou s'il multiplie. Le premier avance à pas constants, le second à pas doublants — l'un a besoin de n pas pour arriver, l'autre du logarithme.

🧠 Quiz éclair

Ces deux boucles se ressemblent. Ont-elles le même ordre ?

int i = n;
while (i > 0) { i = i - 2; }      // (a)

int j = n;
while (j > 0) { j = j / 2; }      // (b)

Non. En (a), i descend de deux en deux : il faut environ n/2 tours, et la constante 1/2 s'efface en notation asymptotique — donc Θ(n).

En (b), j est divisé par deux à chaque tour : il faut environ log₂ n tours pour arriver à zéro — donc Θ(log n). Sur un million d'éléments, cela fait cinq cent mille tours contre vingt.

✍️ Exercice de lecture

Détermine l'ordre de croissance de cet algorithme, en suivant les quatre gestes.

int i = 1;
while (i <= n) {
  int j = 1;
  while (j <= i) {
    j = j + 1;
  }
  i = 2 * i;
}
Voir le corrigé

Geste 1 — le baromètre : j = j + 1, l'instruction la plus interne.

Geste 2 — le compte : la boucle externe est multiplicative, donc i prend les valeurs 1, 2, 4, 8, … jusqu'à n ; il y a environ log₂ n + 1 tours. Pour chacun, la boucle interne est additive et tourne i fois. Le total est donc la somme des puissances de deux, de 2⁰ jusqu'à n.

Geste 3 — le formulaire : c'est une série géométrique. La somme des puissances de deux jusqu'à 2k vaut 2k+1 − 1, ce qui donne ici 2n − 1.

Geste 4 — la conclusion : T(n) ∈ Θ(n). Et voilà le résultat contre-intuitif de l'exercice : une boucle externe logarithmique et une boucle interne linéaire donnent un total linéaire, pas n log n. La dernière itération à elle seule coûte déjà n, et toutes les précédentes réunies n'en coûtent pas plus.

Les sommes qu'on recroise

Le geste 2 produit presque toujours une somme, et le geste 3 consiste à la reconnaître. Il n'y en a qu'une poignée, elles sont sur le formulaire officiel — celui qu'on a le droit d'écrire sur sa feuille manuscrite —, et c'est un investissement rentable que de savoir laquelle sert quand.

La plus fréquente de toutes est la somme des n premiers entiers, qu'on appelle somme de Gauss.

i=1 n i = n(n+1) 2

Elle se retrouve en dix secondes si on l'oublie, et la manière de la retrouver mérite d'être connue pour elle-même : on écrit la somme deux fois, une fois dans un sens, une fois dans l'autre, et on les additionne colonne par colonne.

La somme de Gauss, écrite deux fois tête-bêche
la somme, dans l'ordre croissant
1
2
3
4
5
6
la même somme, dans l'ordre décroissant
6
5
4
3
2
1
le total de chaque colonne
7
7
7
7
7
7
La rangée du bas porte le total de la colonne au-dessus d'elle, et ce total est le même partout : c'est ce que la mise tête-bêche garantit. Additionner ces totaux donne donc deux fois la somme cherchée, d'où la division par deux dans la formule.

Vérifie sur les nombres de la figure : la somme de 1 à 6 vaut 21, et la formule donne 6 × 7/2, soit 21. Cette vérification numérique sur une petite valeur est à faire systématiquement en examen — elle ne coûte rien et elle attrape les erreurs de recopie.

Les autres sommes du formulaire, dans l'ordre où on les rencontre :

La sommeSa forme ferméeSon ordre
de 1, pour i de l à uu − l + 1Θ(u − l)
de i, pour i de 1 à n
somme de Gauss
n(n + 1)/2Θ(n²)
de i², pour i de 1 à nn(n + 1)(2n + 1)/6Θ(n³)
de aᵢ, avec aᵢ = aᵢ₋₁ + r
série arithmétique
n·a₁ + n(n − 1)·r/2Θ(n²)
de rⁱ, pour i de 0 à n
série géométrique
(rⁿ⁺¹ − 1)/(r − 1), r ≠ 1Θ(rⁿ)
de 2ⁱ, pour i de 0 à n2ⁿ⁺¹ − 1Θ(2ⁿ)
de i·2ⁱ, pour i de 1 à n(n − 1)·2ⁿ⁺¹ + 2Θ(n·2ⁿ)
de 1/i, pour i de 1 à n
série harmonique
≈ ln n + 0,5772Θ(log n)

Quatre de ces lignes portent un nom, et les énoncés d'examen les emploient sans les expliquer. Une somme est arithmétique quand chaque terme s'obtient en ajoutant toujours la même raison au précédent — c'est le cas d'une boucle à incrément constant. Elle est géométrique quand on multiplie par une raison constante — c'est le cas d'une boucle qui double. Et la harmonique, la somme des inverses, est celle qui surprend : elle a l'air de converger et elle n'en fait rien, elle croît comme un logarithme. On la rencontre dès qu'on analyse un algorithme en moyenne.

La série géométrique mérite un mot de plus, parce qu'elle est la plus contre-intuitive. 1 + 2 + 4 + 8 + 16 fait 31, soit 2⁵ − 1. Autrement dit : la somme de toutes les puissances de deux jusqu'à un rang donné est à peine plus grande que la dernière d'entre elles. C'est exactement ce qui explique le corrigé surprenant de l'exercice précédent, et c'est un fait qu'on réutilisera pour analyser les arbres et les monceaux.

⚠️ Piège fréquent

Le « plus un » de la borne inférieure. La somme de 1 pour i allant de l à u compte u − l + 1 termes, pas u − l. On oublie le « plus un » une fois sur deux, et l'erreur se propage silencieusement jusqu'à la conclusion.

Le test qui l'attrape à coup sûr, en trois secondes : pose l = u. Tu dois obtenir 1, puisqu'il reste un seul terme. Si ta formule donne 0, il manque le « plus un ».

✍️ Exercice de lecture

Calcule, en fonction de n, la somme double où l'indice interne part de l'indice externe : la somme, pour i de 1 à n, de la somme de 1 pour j allant de i à n.

Voir le corrigé

La somme interne d'abord. Pour i fixé, on compte les valeurs i, i + 1, …, n, ce qui fait n − i + 1 termes. Le « plus un » du piège ci-dessus est exactement ici. Contrôle : pour i = n, on obtient 1, ce qui est correct.

Puis la somme externe. Il reste à sommer n − i + 1 pour i de 1 à n. On découpe par linéarité en trois morceaux : la somme de n (qui vaut n², puisqu'on additionne n fois la constante n), moins la somme de i (qui vaut n(n + 1)/2), plus la somme de 1 (qui vaut n).

On combine : n² − n(n + 1)/2 + n, qu'on met au même dénominateur pour obtenir (2n² − n² − n + 2n)/2, soit n(n + 1)/2.

Conclusion : la somme double vaut n(n + 1)/2, donc elle est en Θ(n²). Joli résultat : c'est la somme de Gauss, atteinte par un chemin plus long. On aurait pu le voir venir — la somme double compte les couples (i, j) avec j ≥ i, c'est-à-dire la moitié d'un carré.

Stirling, et le logarithme du factoriel

La factorielle apparaît plus souvent qu'on ne le croit — dès qu'on énumère des permutations, dès qu'on compte des arrangements, et dès qu'on additionne des logarithmes. Le problème est qu'elle ne ressemble à aucune des fonctions de la hiérarchie : on ne sait pas quoi en faire tant qu'on ne l'a pas traduite. C'est le rôle de la formule de Stirling.

n! 2πn · (ne) n

Lue symbole par symbole : la racine est un petit facteur correctif, qui ne croît que comme la racine de n et deviendra négligeable ; le second facteur est le terme dominant, n divisé par e, le tout élevé à la puissance n. On voit déjà pourquoi la factorielle explose : c'est une puissance dont la base et l'exposant grandissent tous les deux.

Mais ce n'est pas n! qu'on rencontre dans une analyse de boucle — c'est son logarithme. Une boucle externe linéaire dont l'interne est logarithmique donne la somme des log i, et une somme de logarithmes est le logarithme d'un produit : log 1 + log 2 + … + log n n'est rien d'autre que log(n!). Applique le logarithme à la formule ci-dessus : le facteur correctif devient un terme en log n, négligeable, et le terme dominant devient n log n − n log e. Il reste :

log (n!) Θ( n logn )

C'est le résultat à retenir de Stirling pour ce cours. Tout le reste de la formule ne sert qu'à l'établir. Dès que ton comptage aboutit à log(n!), tu écris Θ(n log n) et tu passes à la suite.

🔗 Pont — pourquoi n log n est un plancher

Ce résultat déborde largement de son chapitre. Trier n éléments, c'est choisir l'une de leurs n! permutations possibles. Chaque comparaison ne rapporte qu'une réponse binaire, donc ne peut au mieux que diviser en deux l'ensemble des candidates. Il faut donc au moins log₂(n!) comparaisons — c'est-à-dire, par Stirling, au moins de l'ordre de n log n.

Autrement dit : aucun tri par comparaisons ne peut battre Θ(n log n), jamais, quelle que soit son astuce. Ce n'est pas un constat sur les tris connus, c'est une borne inférieure démontrée. Le module à venir sur les algorithmes de tri s'ouvrira là-dessus, et la même idée reviendra sur la hauteur minimale d'un arbre binaire.

🧠 Quiz éclair

Quel est l'ordre de croissance de cet algorithme ?

int i = 1;
while (i <= n) {
  int j = 1;
  while (j < i) {
    j = 2 * j;
  }
  i = i + 1;
}

La boucle interne est multiplicative et bornée par i : elle tourne environ log₂ i fois. La boucle externe est additive : i va de 1 à n.

Le total est donc la somme des log₂ i pour i de 1 à n, c'est-à-dire log₂(n!). Par Stirling : T(n) ∈ Θ(n log n).

Compare avec l'exercice de la section précédente, dont le code est presque le même avec les deux boucles échangées : là c'était Θ(n), ici c'est Θ(n log n). Savoir laquelle des deux boucles est multiplicative change la réponse.

Deux familles de propriétés accompagnent Stirling et servent tout le temps. Pour les logarithmes : le log d'un produit est la somme des logs, le log d'un quotient est leur différence, le log d'une puissance fait descendre l'exposant en facteur, et le changement de base se fait par multiplication par une constante. Cette dernière propriété a une conséquence directe : en notation asymptotique, la base du logarithme n'a aucune importance — log₂ n et log₁₀ n ne diffèrent que d'un facteur constant, donc appartiennent au même Θ. C'est pourquoi on écrit simplement log n, sans préciser la base.

Pour les exponentielles, qui sont l'exacte réciproque : additionner les exposants revient à multiplier les puissances, multiplier les exposants revient à empiler les puissances, et soustraire revient à diviser. La quatrième identité est celle qu'on oublie et qui débloque la moitié des démonstrations — b s'écrit toujours a élevé au logarithme de b en base a. C'est elle qui permet de passer d'une écriture exponentielle à une écriture logarithmique quand on est coincé.

Pour les parties entières, qui apparaissent dès qu'une boucle divise par deux : la partie entière par défaut de x est le plus grand entier qui ne le dépasse pas, celle par excès est le plus petit qui l'atteint, et les deux encadrent toujours x à moins d'une unité près. Deux identités reviennent souvent : la somme des deux moitiés entières de n redonne n exactement, et le nombre de bits nécessaires pour écrire n est la partie entière par défaut de log₂ n, plus un. En notation asymptotique, on les efface : elles ne changent le compte que d'une unité.

Quand les constantes gagnent

Le module se termine sur son propre garde-fou, parce qu'un outil dont on ne connaît pas les limites finit par se retourner contre celui qui s'en sert. L'analyse asymptotique ignore les constantes — c'est sa force, et c'est aussi la seule chose qu'on peut lui reprocher.

Regarde ces deux fonctions, tirées d'un exercice du cours. La première est linéaire mais traîne trois boucles imbriquées bornées par des constantes ; la seconde est quadratique et ne fait presque rien à chaque tour.

comparaison.cpp
void a(int n) {
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < 100; j++)          // 100 : une CONSTANTE
      for (int k = 0; k < 300; k++)        // 300 : une constante aussi
        std::cout << "abcde\n";           // ← 30 000 × n exécutions
  }
}

void b(int n) {
  for (int i = 0; i < n; i++)
    for (int j = 0; j < i; j++)            // bornée par i : somme triangulaire
      std::cout << "12345\n";             // ← n(n − 1)/2 exécutions
}

Deux boucles imbriquées ne donnent pas toujours Θ(n²) — tout dépend de ce qui borne la boucle interne, une constante ou l'indice externe.

Le compte donne environ 30 000 n pour la première, donc Θ(n), et n(n − 1)/2 pour la seconde, donc Θ(n²). L'analyse asymptotique tranche sans hésiter en faveur de la première. Cherchons maintenant le point où les deux se croisent : en résolvant 30 000 n = n(n − 1)/2, on trouve n aux alentours de soixante mille.

Autrement dit : sur toute entrée plus petite que soixante mille éléments, l'algorithme « moins efficace » est le plus rapide des deux. Et sur beaucoup de problèmes réels, l'entrée ne dépasse jamais soixante mille éléments.

Ce n'est pas une critique de l'analyse asymptotique, c'est son mode d'emploi. Elle répond à la question « lequel tiendra quand ça grossira ? », qui est presque toujours la bonne question — mais pas toujours. Quand tu sais que n restera petit, et que tu connais les constantes, le calcul explicite l'emporte sur la notation. La règle qu'on en tire est simple : le Θ décide de l'algorithme, les constantes décident de son implémentation.

🧠 Quiz éclair

Deux programmes garantissent un pire cas d'au plus 150 n log₂ n et n² opérations. Lequel offre la meilleure garantie à n = 100 ? Et le second peut-il être plus rapide que le premier sur une entrée donnée ?

À n = 100 : le premier garantit environ 150 × 100 × 6,64, soit près de cent mille opérations ; le second en garantit dix mille. Le second offre la meilleure garantie — la constante 150 domine largement à cette échelle. Le rapport s'inverse pour les grandes entrées, l'ordre finissant toujours par l'emporter sur la constante.

Et oui, le second peut être plus rapide sur une entrée donnée, même très grande. Une borne de pire cas est une majoration : elle promet de ne jamais dépasser, elle ne prédit pas ce qu'on prendra. Rien n'interdit au second de terminer très vite sur une entrée favorable.

🎓 À l'examen

Cette matière est celle de la semaine 1 et elle est évaluée à l'intra. Elle n'est jamais isolée : chaque structure de données du reste de la session se conclut par une analyse asymptotique, et les points s'y perdent de la même façon toute la session.

Le 🗂️ qui clôt ce module est la matière de la feuille manuscrite que l'examen autorise : ce qui se recopie et se consulte, jamais ce qui se redémontre. Le formulaire de sommations est la première chose à y écrire.

« Donnez la définition formelle de f(n) ∈ O(g(n)). »

Il existe deux constantes positives c et n0 telles que f(n) ≤ c · g(n) pour tout n ≥ n0. Les deux constantes sont exigées : une réponse qui oublie le seuil n0 est incomplète, parce que c'est lui qui porte le mot « asymptotique ».

« Cet algorithme est en O(n³). Est-il plus lent que celui-là, qui est en O(n²) ? »

On ne peut pas conclure. Ce sont deux plafonds, et rien ne dit qu'ils sont serrés : le premier pourrait être en Θ(1). Pour comparer deux algorithmes, il faut des Θ des deux côtés. C'est la question piège classique de l'intra, et la bonne réponse commence par « on ne peut pas conclure ».

« Déterminez l'ordre de croissance de cette boucle. »

Récite les quatre gestes à voix haute, dans l'ordre : le baromètre, le compte écrit comme une somme, la simplification par le formulaire, la conclusion en Θ. Un correcteur donne des points à chacune des quatre étapes, même quand le résultat final est faux — sauter directement au Θ, c'est renoncer à trois quarts des points.

À retenir

Un algorithme n'a pas de vitesse, il a une loi de croissance, et cette loi est la seule chose qui survive au changement de machine. On l'obtient en comptant les exécutions d'une seule opération bien choisie — le baromètre —, puis en jetant délibérément les constantes pour ne garder que la forme. O plafonne, Ω plancherise, Θ encadre : seul Θ permet de comparer deux algorithmes, et c'est pour ça qu'on l'annonce dès qu'on le connaît.

Sur une boucle, quatre gestes suffisent, toujours les mêmes : le baromètre, le compte écrit en somme, la simplification par le formulaire, la conclusion. Et un réflexe précède les quatre — regarder si l'indice avance par addition ou par multiplication, parce que cette seule observation sépare Θ(n) de Θ(log n).

Et ailleurs : ce module n'est pas un chapitre, c'est l'outil de mesure de tous les autres. Chaque structure de données de la session — piles, files, tables de dispersion, arbres, monceaux, graphes — sera présentée puis mesurée avec exactement ce vocabulaire, et le choix entre deux structures se fera toujours en comparant leurs Θ. Le résultat de Stirling ressortira intact le jour des tris. Et hors du cours, c'est la compétence qui permet de lire une documentation technique : quand une bibliothèque annonce une insertion « en temps constant amorti », tu sais désormais exactement ce qu'elle promet et ce qu'elle ne promet pas.

🗂️ L'aide-mémoire
Les deux constantes de la définition de O
c et n0. Oublier le seuil est l'erreur la plus courante : c'est lui qui porte le mot « asymptotique »
Ce que Θ ajoute à O
la borne inférieure — Θ, c'est O et Ω. Comparer deux algorithmes demande des Θ des deux côtés ; avec des O, la réponse est souvent « on ne peut pas conclure »
Les trois verdicts du test par limite
L = 0 → f ∈ O(g) sans être Θ ; 0 < L < ∞ → Θ ; L = ∞ → Ω sans être Θ
Ce qu'on regarde AVANT de compter une boucle
l'opérateur qui met l'indice à jour : additif (i++, i += 2) → Θ(n) ; multiplicatif (i *= 2, i /= 2) → Θ(log n)
Le nombre de termes d'une somme de l à u
u − l + 1. Le test qui l'attrape : poser l = u doit donner 1, jamais 0
La somme des n premiers entiers
n(n + 1)/2 — et le contrôle numérique : pour n = 6, la formule donne 21
La somme des puissances de 2 jusqu'à 2ⁿ
2ⁿ⁺¹ − 1, c'est-à-dire à peine plus que le dernier terme. C'est ce qui rend linéaire une boucle qu'on croit linéarithmique
Ce que Stirling sert à traduire
log(n!) ∈ Θ(n log n). C'est son seul usage dans ce cours — le reste de la formule ne sert qu'à l'établir
L'ordre de f + g
Θ(max(f, g)) — seul le terme dominant survit. n² + n log n est en Θ(n²)
Quand la base du logarithme compte
jamais dans un Θ (elle n'est qu'un facteur constant) ; toujours dans un calcul chiffré, où log₂ et log₁₀ ne donnent pas le même nombre