Bases de la complexité
Introduction
Vous savez maintenant écrire un parcours séquentiel. Mais vous écrirez bientôt des algorithmes plus subtils, et la première question d'un informaticien devant un algorithme n'est pas « est-ce qu'il marche ? » — c'est « combien coûte-t-il ? ». Combien d'opérations effectue-t-il selon la taille des données ? Si je double l'entrée, le temps double-t-il, quadruple-t-il, ou augmente-t-il à peine ? Ce cours pose le socle de cette analyse. Pas de théorème lourd : juste une intuition claire et une notation simple, qui vous serviront dans tout le reste du chapitre.
Pourquoi mesurer ?
Imaginez deux programmes qui font la même chose : chercher un nom dans un annuaire de 10 millions d'entrées. Le premier met 3 secondes, le second met 3 minutes. Vous préférerez le premier, évidemment. La complexité, c'est l'outil qui permet de prédire cette différence avant même d'exécuter le code, en analysant la structure de l'algorithme.
Taille de l'entrée
Avant de compter, il faut décider de ce qu'est la taille . C'est en général évident :
- pour un tableau ou une liste : = le nombre d'éléments,
- pour une chaîne de caractères : = sa longueur,
- pour un entier : = sa valeur (parfois son nombre de chiffres).
Trois ordres de grandeur typiques
Coût constant —
Le coût ne dépend pas de la taille. Que tableau ait 10 ou 10 millions
d'éléments, l'opération prend le même temps.
def premier(tableau):
return tableau[0] # un seul accès, peu importe la taille
Coût linéaire —
Le coût est proportionnel à . Si on double la taille, le temps double. Tout parcours séquentiel du cours précédent est dans ce cas.
def somme(tableau):
total = 0
for v in tableau: # exactement n tours
total += v
return total
Coût quadratique —
Le coût est proportionnel à . Si on double la taille, le temps est multiplié par 4. On l'obtient typiquement avec deux boucles imbriquées qui parcourent chacune le tableau.
def doublons(tableau):
"""Renvoie True si tableau contient au moins un doublon."""
n = len(tableau)
for i in range(n):
for j in range(i + 1, n):
if tableau[i] == tableau[j]:
return True
return False
Le nombre de comparaisons est de l'ordre de — ce qui reste proportionnel à .
Si un algo en O(n²) met 1 seconde pour n=1000, combien de temps environ pour n=10000 ?
La notation grand-O, en douceur
On a écrit , . Que signifie ce symbole ?
L'idée : on néglige les constantes et les détails de petite taille pour ne garder que l'ordre de grandeur quand devient grand. Par exemple, un algorithme qui fait opérations et un autre qui en fait ont le même comportement asymptotique (c'est-à-dire la même allure quand devient grand) — tous deux sont en . Quand atteint 1 million, le « +7 » et le « ×3 » sont sans importance face au grand .
Vous rencontrerez aussi au cours 4 (dichotomie) — c'est un coût encore plus petit que , qui croît extrêmement lentement.
Comparer les ordres de grandeur
| 10 | ≈ 3 | 10 | 100 |
| 100 | ≈ 7 | 100 | 10 000 |
| 1 000 | ≈ 10 | 1 000 | 1 000 000 |
| 1 000 000 | ≈ 20 | 1 000 000 |
Pour un million d'éléments, un algorithme logarithmique finit en 20 étapes ; un algorithme linéaire en un million ; un algorithme quadratique en mille milliards — irréalisable en pratique.
Cas pire, cas moyen
Tous les algorithmes n'effectuent pas le même nombre d'opérations sur toutes les entrées de même taille. La recherche linéaire peut :
- trouver dès la 1ère case (cas favorable, 1 comparaison) ;
- trouver à la dernière case ou ne rien trouver (cas pire, comparaisons).
On parle souvent du cas pire car c'est la garantie maximale offerte par l'algorithme. Quand on dit « la recherche linéaire est en », c'est au pire — au mieux, elle est en .
Au mieux, combien d'opérations effectue une recherche linéaire qui trouve sa cible ?
Pièges courants
- Compter en secondes au lieu de compter des opérations : trompeur, car dépendant de la machine.
- Garder les constantes dans la notation : on écrit , jamais .
- Confondre cas pire et cas moyen : à l'oral, préciser quand c'est ambigu.
Pour aller plus loin
Vous allez vérifier ces ordres de grandeur dans les cours suivants : les tris du cours 3 sont en , la dichotomie du cours 4 est en . La différence de performance entre les deux dernières est colossale — un algorithme dichotomique sur un milliard d'éléments tient en 30 étapes seulement.