Séance 11 · Partie B · Algorithmique et maths
Complexité,
recherche et tris.
Deux programmes donnent le même résultat. L’un répond en une milliseconde, l’autre en trois jours. Aujourd’hui, on apprend à prévoir lequel avant de le lancer.
Durée : 2 séances · Objectifs : compter les opérations, la notation O(·), recherche linéaire et dichotomique, tri par sélection, insertion, fusion ; mesurer et comparer.
Ce que vous saurez faire à la fin
- Dire si un algorithme est en O(1), O(log n), O(n), O(n log n) ou O(n²), et ce que cela implique.
- Écrire une recherche dichotomique correcte, et en justifier la terminaison.
- Écrire trois algorithmes de tri et expliquer pourquoi l’un est fondamentalement meilleur.
- Mesurer proprement et interpréter une courbe de temps.
01 / Compter
Le temps ne se mesure pas, il se compte
Quand n double :
sommeprend 2 fois plus longtemps : linéaire, O(n).pairesprend 4 fois plus longtemps : quadratique, O(n²).
La mesure dépend de la machine, du langage, de la charge. Le comptage ne dépend de rien : c’est lui qu’on étudie.
On ne garde que le terme dominant, sans constante : n + 2 → O(n), 3n² + 5n → O(n²).
Pourquoi ignorer les constantes ?
Parce qu’un ordinateur deux fois plus rapide divise la constante par deux, mais ne change pas la forme de la courbe. Un algorithme O(n²) sur un supercalculateur finira toujours par perdre contre un O(n log n) sur un Raspberry Pi, pour n assez grand. La notation O décrit le comportement asymptotique : ce qui se passe quand n devient grand. En concours, on vous demandera « quelle est la complexité ? » et on attend une justification par comptage, pas une mesure.
01 / Compter
Les classes qui comptent
| Notation | Nom | Si n = 1 000 000 | Exemple |
|---|---|---|---|
| O(1) | constant | 1 | Accéder à liste[i], dict[cle] |
| O(log n) | logarithmique | ≈ 20 | Dichotomie |
| O(n) | linéaire | 1 000 000 | Parcourir une liste, x in liste |
| O(n log n) | quasi linéaire | ≈ 20 000 000 | Bons tris (sorted) |
| O(n²) | quadratique | 10¹² | Deux boucles imbriquées, mauvais tris |
| O(2ⁿ) | exponentiel | ≈ 10³⁰¹⁰³⁰ | Essayer tous les sous-ensembles |
À un milliard d’opérations par seconde : O(n²) pour un million = 17 minutes. O(2ⁿ) pour n = 100 = plus que l’âge de l’univers.
Le logarithme, en une phrase
log₂(n) est le nombre de fois qu’on peut diviser n par 2 avant d’arriver à 1. log₂(1 000 000) ≈ 20, log₂(10⁹) ≈ 30. Un algorithme en O(log n) qui « coupe en deux » à chaque étape est presque aussi rapide qu’un accès direct. C’est le pouvoir de la dichotomie.
02 / Chercher
Recherche linéaire : regarder chaque case
Pire cas, meilleur cas, cas moyen
Meilleur : 1 comparaison (la cible est en tête). Pire : n (la cible est en queue ou absente). Moyen : n/2 si elle est présente à une position aléatoire. On raisonne presque toujours sur le pire cas : c’est la garantie. Un robot doit réagir à temps même dans le pire cas. (Le global ici est une facilité pour compter ; en vrai code, on renverrait le compte.)
02 / Chercher
Dichotomie : couper en deux, si la liste est triée
Un million d’éléments, 20 étapes maximum.
Invariant : à chaque tour, si la cible est dans la liste, elle est entre bas et haut.
Terminaison : haut − bas diminue strictement à chaque tour (on exclut le milieu), donc la boucle s’arrête.
Invariant + terminaison = preuve de correction. C’est ce qu’on attend en concours.
Le jeu du nombre mystère
« Je pense à un nombre entre 1 et 1000 » : avec « plus grand / plus petit », 10 questions suffisent (2¹⁰ = 1024). Chaque question divise l’espace par deux. C’est exactement la dichotomie, et c’est pour cela que le nombre d’étapes est log₂(n). La condition « liste triée » est indispensable : sur une liste en désordre, couper en deux ne dit rien. Trier coûte O(n log n) ; si on cherche souvent, ça vaut le coup.
03 / Trier
Tri par sélection : chercher le minimum, le placer
Comptage
Tour i : n − i − 1 comparaisons. Total : (n−1) + (n−2) + … + 1 = n(n−1)/2 ≈ n²/2. Que la liste soit déjà triée ou non, le nombre de comparaisons est le même : O(n²) toujours. Avantage : très peu d’échanges (n − 1 au plus), ce qui compte si déplacer un élément est coûteux.
03 / Trier
Tri par insertion : comme on range des cartes
Quand l’utiliser
Sur une liste presque triée, l’insertion est linéaire : chaque élément ne se décale que de quelques cases. C’est le cas d’un flux de mesures où seule la dernière valeur est nouvelle. Les bibliothèques réelles (dont celle de Python) utilisent le tri par insertion pour les petits morceaux de moins de 32 éléments, où il bat tout le monde grâce à sa simplicité.
03 / Trier
Tri fusion : diviser pour régner
Pourquoi n log n
On coupe en deux jusqu’à des listes de 1 élément : log₂(n) niveaux de découpe. À chaque niveau, fusionner tous les morceaux coûte n opérations au total. D’où n × log₂(n). La fonction qui s’appelle elle-même est récursive : c’est le sujet de la séance 12. Le tri fusion est le premier algorithme « diviser pour régner » de l’histoire (von Neumann, 1945), et il est optimal : aucun tri par comparaison ne peut faire mieux que O(n log n) dans le pire cas — un théorème qu’on démontre en prépa avec un argument de comptage sur les n! ordres possibles.
03 / Trier
La course
Lire le tableau
Quand n double : sélection et insertion ×4 (quadratique), fusion ×2 et un peu plus (n log n), sorted pareil mais 50 fois plus vite car écrit en C. Extrapolez : pour n = 1 000 000, la sélection prendrait des heures, la fusion quelques secondes. Refaites la mesure avec une liste déjà triée : l’insertion devient la plus rapide. Il n’y a pas de « meilleur tri » absolu, il y a le bon tri pour les bonnes données.
04 / Défis
Défi ★ — Compter et prédire
Consigne
Pour chaque fonction, donner la complexité en O(·) puis vérifier en mesurant pour n = 1000, 2000, 4000.
Correction
f1 : O(1) — une formule, quel que soit n. f2 : O(n). f3 : O(n²) — 0 + 1 + … + (n−1) = n(n−1)/2 tours. f4 : O(log n). f5 : O(n log n) — n tours, chacun avec log₂(n) doublements. Les mesures confirment : f1 constant, f2 ×2, f3 ×4, f4 quasi constant, f5 un peu plus que ×2. Notez que f1 et f2 calculent la même chose : la formule de Gauss remplace une boucle. Trouver une formule fermée est la meilleure optimisation possible.
04 / Défis
Défi ★★ — Dichotomie sur une fonction
Consigne
La dichotomie marche sur n’importe quoi de « trié », pas seulement des listes. 1. racine_entiere(n) : le plus grand entier r tel que r² ≤ n, par dichotomie sur r entre 0 et n, sans flottants. Tester avec n = 10¹⁸. 2. premier_superieur(liste_triee, seuil) : la position du premier élément > seuil (ou len si aucun). Combien de mesures dépassent 50 dans une liste triée de 10⁶ valeurs, en 20 étapes ?
Correction
def racine_entiere(n):
bas, haut = 0, n
while bas < haut:
milieu = (bas + haut + 1) // 2 # arrondi vers le haut : évite la boucle infinie
if milieu * milieu <= n:
bas = milieu
else:
haut = milieu - 1
return bas
def premier_superieur(liste, seuil):
bas, haut = 0, len(liste)
while bas < haut:
milieu = (bas + haut) // 2
if liste[milieu] <= seuil:
bas = milieu + 1
else:
haut = milieu
return bas10⁹ pour 10¹⁸, 4, 5. L’arrondi vers le haut dans racine_entiere est le détail qui fait tout : avec (bas + haut) // 2 et bas = milieu, on peut boucler sur bas = haut − 1. La deuxième fonction s’appelle bisect_right dans le module bisect : elle sert à insérer dans une liste triée en la gardant triée. Ces deux variantes (« plus grand tel que » / « premier tel que ») couvrent 90 % des usages de la dichotomie.
04 / Défis
Défi ★★★ — Esprit prépa : le tri rapide et son pire cas
Consigne
Le tri rapide (quicksort, Hoare 1961) : choisir un pivot, mettre à gauche les plus petits, à droite les plus grands, trier les deux côtés récursivement. Écrire tri_rapide(liste) avec le premier élément comme pivot. Mesurer sur une liste aléatoire de 5000 éléments, puis sur une liste déjà triée de 5000 éléments. Expliquer la différence. Corriger en choisissant le pivot au hasard.
Correction et ouverture
def tri_rapide(liste):
if len(liste) <= 1:
return liste
pivot = liste[0]
petits = [x for x in liste[1:] if x < pivot]
grands = [x for x in liste[1:] if x >= pivot]
return tri_rapide(petits) + [pivot] + tri_rapide(grands)Sur une liste triée, le pivot est toujours le minimum : « petits » est vide, « grands » a n − 1 éléments, et on refait n niveaux au lieu de log n → O(n²), et une profondeur de récursion n qui dépasse la limite de Python (~1000). Avec pivot = random.choice(liste), le pire cas devient improbable : O(n log n) en moyenne, quelle que soit l’entrée. Le tri rapide est en pratique le plus rapide des tris (bonne utilisation du cache, séance 01), mais sans garantie de pire cas — contrairement au tri fusion. Les bibliothèques réelles combinent les deux (introsort) ou utilisent Timsort. Question d’oral classique : « quelle est la complexité du tri rapide ? » Réponse attendue : O(n log n) en moyenne, O(n²) au pire, et pourquoi.
05 / Vérification
Combien d’étapes au maximum pour trouver un élément par dichotomie dans une liste triée d’un million d’éléments ?
Deux questions supplémentaires
1. Pourquoi ne peut-on pas faire de dichotomie sur une liste non triée ? Parce que comparer avec le milieu ne dit pas de quel côté chercher.
2. Le tri fusion est-il « sur place » ? Non : il crée de nouvelles listes (O(n) de mémoire supplémentaire). La sélection et l’insertion, oui.
Référence
Les mots à retenir
| Mot | Définition |
|---|---|
| Complexité | Nombre d’opérations en fonction de la taille n de l’entrée. |
| O(f(n)) | « Au plus proportionnel à f(n) pour n grand ». |
| Pire cas | L’entrée qui maximise le nombre d’opérations. |
| Invariant | Propriété vraie à chaque tour de boucle ; sert à prouver la correction. |
| Terminaison | Preuve que la boucle s’arrête (une quantité entière décroît strictement). |
| Diviser pour régner | Couper le problème, résoudre les morceaux, recombiner. |
| Sur place | Sans mémoire supplémentaire proportionnelle à n. |
Pour continuer
Vous savez prévoir le temps
Le tri fusion s’appelait lui-même. Cette idée — la récursivité — mérite une séance entière.
À faire chez soi
- Refaire les trois tris de mémoire, avec leurs invariants en commentaire.
- Donner la complexité de : chercher le maximum ; vérifier qu’une liste est triée ; compter les doublons avec deux boucles ; compter les doublons avec un dictionnaire.
- Lire l’article Wikipédia « Tri fusion » et comparer avec votre version.