Algorithmique

L'algorithmique et ses applications

Chercher, trier, s'appeler soi-même, compresser un texte, classer le Web et chiffrer un secret : cinq planches pour regarder travailler les algorithmes, compter leurs opérations et vérifier qu'ils tiennent leurs promesses.

Qu'est-ce qu'un algorithme ?

Un algorithme est une suite finie d'instructions précises qui, appliquée à des données (l'entrée), produit un résultat (la sortie) en un nombre fini d'étapes. Le mot vient du nom du savant persan al-Khwârizmî (IXe siècle), dont les traités ont fait connaître en Europe le calcul avec les chiffres indo-arabes. Une recette de cuisine, la division posée apprise à l'école ou l'itinéraire calculé par un GPS sont des algorithmes ; un programme n'est qu'un algorithme écrit dans une langue que la machine comprend.

Devant un algorithme, on se pose toujours trois questions :

Termine-t-il ? — c'est la terminaison : il ne doit pas tourner indéfiniment.
Donne-t-il la bonne réponse ? — c'est la correction, qu'on démontre, puis qu'on contrôle sur des exemples.
Combien coûte-t-il ? — c'est la complexité : le nombre d'opérations élémentaires en fonction de la taille n des données.

On écrit par exemple O(n²) (« grand O de n carré ») pour dire que le nombre d'opérations croît au plus comme n², à un facteur constant près. La figure ci-dessous montre pourquoi cette question domine toutes les autres : on y lit le temps de calcul d'une machine qui exécute un milliard d'opérations par seconde, selon la complexité de l'algorithme.

La course des complexités. Les deux axes sont logarithmiques : chaque graduation multiplie la valeur par 10. Le curseur vertical parcourt les tailles de 1 à un milliard.
taille n = 10^1
Taille n
—
nombre de données à traiter
log₂ n — dichotomie
—
planche 1
n log₂ n — tri fusion
—
planche 2
n² — tri par sélection
—
planche 2
2ⁿ — tout essayer
—
essayer tous les sous-ensembles
À retenir. Un ordinateur mille fois plus rapide ne fait descendre les courbes que de trois graduations ; passer d'un algorithme en 2ⁿ à un algorithme en n² en fait gagner des centaines. Le progrès décisif vient presque toujours d'une meilleure idée, rarement d'une machine plus rapide.
1

Chercher : lire tout, ou couper en deux ?

Question : pour retrouver un nom dans un annuaire de n noms rangés par ordre alphabétique, faut-il les lire un par un ? La recherche séquentielle compare la cible à chaque case, dans l'ordre. La recherche dichotomique (du grec « couper en deux ») regarde la case du milieu, élimine la moitié qui ne peut pas contenir la cible, et recommence sur l'autre moitié.

séquentielle : (n + 1)/2 comparaisons en moyenne  ·  dichotomique : au plus ⌊log₂ n⌋ + 1

Ici ⌊x⌋ désigne la partie entière de x, et log₂ n le nombre de fois qu'on peut diviser n par 2 avant de tomber sous 1. Chaque case ci-dessous contient une valeur ; les valeurs sont rangées par ordre croissant.

En haut, la lecture case par case ; en bas, l'intervalle encore possible (bleu clair) se réduit de moitié à chaque comparaison. Rouge : case comparée ; vert : cible trouvée ; gris : case éliminée.
nombre de cases n 63
position de la cible 41
vitesse 4
Séquentielle — cette recherche
0
comparaisons effectuées
Dichotomique — cette recherche
0
comparaisons effectuées
Moyenne séquentielle mesurée
—
—
Pire cas dichotomique mesuré
—
—
Pire cas de la dichotomie, mesuré en lançant la recherche sur toutes les cibles possibles (bleu, de 1 à 1 024 cases), contre l'escalier de la formule ⌊log₂ n⌋ + 1 (vert pointillé). Point rouge : un million de cases.
Escalier mesuré contre formule
—
tailles de 1 à 1 024, toutes les cibles essayées
Un million de cases
calcul…
—
Ce que l'on voit. Doubler la taille de l'annuaire double le travail de la recherche séquentielle, mais n'ajoute qu'une seule comparaison à la dichotomie : chaque marche de l'escalier correspond à une puissance de 2. Le prix à payer est que les données soient triées d'avance — d'où la planche suivante.
2

Trier : cinq méthodes, deux familles

Question : comment ranger n valeurs par ordre croissant, et combien de comparaisons cela coûte-t-il ? Voici cinq méthodes classiques, chacune décrite en une phrase.

Une inversion est un couple de valeurs mal rangées l'une par rapport à l'autre ; une liste triée n'en a aucune, une liste inversée en a n(n − 1)/2. Elles servent de contrôle : le tri à bulles fait exactement un échange par inversion.

Chaque barre est une valeur. Rouge : les deux valeurs comparées ; orange : valeurs déplacées ; violet : le pivot du tri rapide ; vert : liste triée.
méthode
ordre de départ
n 40
vitesse 3
Comparaisons
0
deux valeurs confrontées
Déplacements
0
échanges ou recopies
Inversions au départ
—
—
Contrôle
—
—

La course : comparaisons mesurées selon la taille

Moyennes mesurées sur des listes au hasard, en double échelle logarithmique : une droite de pente 2 signale un coût en n², une pente proche de 1 un coût en n log n. Pointillés verts : formules exactes du tri par sélection, n(n − 1)/2, et moyenne du tri rapide, 2(n + 1)Hn − 4n.
Pente — sélection
calcul…
—
Pente — insertion
…
moyenne ≈ n²/4
Pente — bulles
…
avec arrêt anticipé
Pente — fusion
…
—
Pente — rapide
…
—
Ce que l'on voit. Sélection, insertion et bulles coûtent de l'ordre de n² comparaisons ; fusion et rapide, de l'ordre de n log₂ n. Mais l'ordre de départ compte : essayez le tri rapide sur une liste inversée — le pivot, toujours la dernière valeur, est alors la plus petite, et la méthode retombe à n(n − 1)/2 comparaisons. À l'inverse, l'insertion sur une liste presque triée devient presque linéaire.
3

La récursivité : se rappeler soi-même

Une fonction récursive est une fonction qui s'appelle elle-même sur un problème plus petit, jusqu'à un problème assez simple pour être résolu directement : le cas de base. C'est la forme naturelle de « diviser pour régner », déjà à l'œuvre dans la dichotomie et le tri fusion.

Les tours de Hanoï

Pour déplacer k disques de A vers C sans jamais poser un disque sur un plus petit : déplacer les k − 1 disques du dessus de A vers B, puis le grand disque de A vers C, puis les k − 1 disques de B vers C. Le nombre de coups vérifie h(k) = 2h(k − 1) + 1, d'où h(k) = 2k − 1.

disques 4
coups/s 2
Coups joués
0
—
Coups illégaux
0
vérifié à chaque coup
Le petit disque
—
—
64 disques, un coup par seconde
—
—

Fibonacci et l'arbre des appels

F(0) = 0, F(1) = 1 et F(n) = F(n − 1) + F(n − 2). Programmée telle quelle, la fonction recalcule sans cesse les mêmes valeurs. La mémoïsation — garder chaque résultat dans un carnet et le relire au lieu de le recalculer — supprime ces doublons.

n 6
Appels dans l'arbre
—
—
F(2) recalculé
—
—
grand calcul : F(N) avec N = 30
Appels naïfs mesurés
—
—
Appels mémoïsés
—
—
Rapport d'un N au suivant
—
—
Ce que l'on voit. La récursivité décrit en trois lignes un algorithme de 255 coups (8 disques). Mais elle peut cacher une explosion : l'arbre de Fibonacci compte 2F(n + 1) − 1 appels, une croissance en φn où φ = 1,618… est le nombre d'or. Retenir les résultats déjà calculés — l'idée de la programmation dynamique — ramène le coût à 2n − 1 appels.
4

Application : compresser un texte avec Huffman

En informatique, chaque caractère occupe ordinairement 8 bits (un octet ; un bit vaut 0 ou 1). Mais pourquoi coder « e », très fréquent, avec autant de bits que « z », très rare ? L'algorithme de Huffman (1952) construit un code de longueur variable : on prend les deux symboles les moins fréquents, on les réunit sous un nœud dont le poids est la somme de leurs effectifs, et l'on recommence jusqu'à n'avoir plus qu'un seul arbre. Le chemin de la racine à une feuille donne le code du caractère : 0 à gauche, 1 à droite.

Le code obtenu est un code préfixe : aucun code n'est le début d'un autre, si bien qu'on décode sans séparateur. Sa qualité se mesure à l'entropie de Shannon H = −Σ p log₂ p (où p est la fréquence de chaque caractère) : aucun code ne peut descendre en moyenne sous H bits par caractère, et Huffman garantit H ≤ L < H + 1, où L est la longueur moyenne de son code.

texte
Chaque feuille (carré) porte un caractère et son effectif ; chaque nœud rond porte la somme de ses deux enfants. Rouge : le nœud que l'on vient de créer ; orange : les deux plus petits poids, qui seront réunis à l'étape suivante. ␣ désigne l'espace.
Huffman
—
—
Codage ordinaire et longueur fixe
—
—
Entropie H ≤ L < H + 1
—
—
Contrôles
—
—

Début du texte codé (chaque caractère suivi de son code) :

Ce que l'on voit. Huffman est optimal parmi les codes qui attribuent un nombre entier de bits à chaque caractère. Sur huit lettres équiprobables, il ne fait pas mieux qu'un code fixe de 3 bits : tout le gain vient de l'inégalité des fréquences, et les effectifs de Fibonacci donnent l'arbre le plus déséquilibré possible. On retrouve Huffman, associé à d'autres méthodes, dans les formats ZIP, PNG, JPEG et MP3.
5

Signature : deux algorithmes qui ont changé Internet

Deux applications à grande échelle, où les idées des planches précédentes — itérer, compter les opérations, exploiter un écart de complexité — deviennent décisives.

Comment classer des milliards de pages ? L'idée de Sergey Brin et Larry Page (1998) : une page est importante si des pages importantes pointent vers elle. On l'imagine avec un surfeur aléatoire qui, à chaque clic, suit au hasard l'un des liens de la page avec la probabilité d (le facteur d'amortissement, 0,85 en pratique), ou saute vers une page quelconque avec la probabilité 1 − d ; d'une page sans lien, il saute toujours au hasard. Le PageRank d'une page est la proportion du temps que le surfeur y passe à long terme.

ri = (1 − d)/N + d Σj → i rj / sj   (sj : nombre de liens sortant de la page j)

On calcule ce classement par trois chemins indépendants : on simule le surfeur ; on itère la formule à partir de valeurs égales (méthode de la puissance) ; on résout exactement le système linéaire de N équations par l'élimination de Gauss.

Le petit Web de huit pages. Taille d'un disque : son PageRank. Point rouge : le surfeur ; un trait pointillé signale un saut au hasard.
En haut, PageRank exact (bleu) et fréquences de visite du surfeur (orange). En bas, erreur de la méthode de la puissance à chaque itération (échelle log), et la droite dk (rouge pointillé).
réseau
d 0,85
sauts/s 4
Page la mieux classée
—
—
Puissance contre Gauss
—
plus grand écart entre les deux calculs
Itérations pour 10⁻¹⁰
—
—
Contraction mesurée
—
—
Surfeur contre valeur exacte
—
—
Ce que l'on voit. Trois chemins, un seul classement : la simulation s'en approche lentement (l'écart diminue comme 1/√(nombre de sauts)), l'itération l'atteint en quelques dizaines de tours, l'élimination de Gauss le donne d'un coup — mais seule l'itération passe à l'échelle de milliards de pages. L'erreur est divisée au moins par 1/d à chaque tour ; dans la « ferme de liens », deux îlots fermés font tomber la contraction exactement sur d, et la page H, vers laquelle pointent toutes les autres pages de son îlot, prend la tête du classement.

Comment envoyer un secret à quelqu'un qu'on n'a jamais rencontré ? Le chiffrement RSA (Rivest, Shamir et Adleman, 1977) repose sur une dissymétrie : multiplier deux nombres premiers est facile, retrouver ces deux nombres à partir de leur produit est extrêmement difficile. On note a mod n le reste de la division de a par n.

  • On choisit deux nombres premiers p et q, on calcule n = pq et φ(n) = (p − 1)(q − 1).
  • On choisit e sans diviseur commun avec φ(n) : la clé publique est (n, e), connue de tous.
  • L'algorithme d'Euclide étendu fournit d tel que e·d mod φ(n) = 1 : c'est la clé privée.
  • On chiffre c = me mod n et l'on déchiffre m = cd mod n, par exponentiation rapide : on lit l'exposant en binaire, on élève au carré à chaque chiffre et on multiplie quand le chiffre vaut 1.
p
q
e
message

Euclide étendu : trouver la clé privée

À chaque ligne, le reste r s'obtient en retranchant q fois la ligne précédente ; le coefficient t suit le même calcul, si bien que r et t·e ont toujours le même reste modulo φ(n) — contrôlé dans la dernière colonne.

Le message chiffré puis déchiffré

Opérations nécessaires selon le nombre de chiffres de n (abscisse logarithmique). Bleu : multiplications pour déchiffrer, par exponentiation rapide ; rouge : divisions pour casser la clé en cherchant un diviseur de n. Points : mesurés sur des clés de 3 à 15 chiffres ; traits : prolongement des mesures.
Clé publique (n, e)
—
—
Clé privée d
—
—
Aller-retour exhaustif
—
tous les messages de 0 à n − 1 chiffrés puis déchiffrés
Déchiffrer un nombre
—
—
Casser cette clé
—
—
Clé de 617 chiffres (RSA-2048)
—
—
Ce que l'on voit. Toute la sécurité tient dans l'écart entre deux complexités : la courbe bleue croît comme le nombre de chiffres, la rouge comme la racine carrée de n. Les meilleures méthodes de factorisation connues font beaucoup mieux que les essais de divisions, mais restent hors de portée pour une clé de 617 chiffres. Un ordinateur quantique assez grand, avec l'algorithme de Shor (1994), changerait la donne — d'où les recherches actuelles sur la cryptographie « post-quantique ».

Repères historiques

  1. vers −300Euclide décrit, au livre VII des Éléments, l'algorithme du plus grand commun diviseur — celui qu'utilise encore RSA.
  2. vers 825Al-Khwârizmî rédige ses traités de calcul indien et d'algèbre ; la forme latine de son nom donnera « algorithme ».
  3. 1843Ada Lovelace publie un programme de calcul des nombres de Bernoulli pour la machine analytique de Charles Babbage.
  4. 1936Alan Turing définit mathématiquement le calcul mécanique et montre que certains problèmes n'ont aucun algorithme.
  5. 1945John von Neumann décrit le tri fusion.
  6. 1952David Huffman invente ses codes de longueur minimale.
  7. 1959Edsger Dijkstra publie son algorithme du plus court chemin.
  8. 1961Tony Hoare publie le tri rapide.
  9. 1968Donald Knuth fait paraître le premier volume de The Art of Computer Programming.
  10. 1971Stephen Cook pose les bases de la NP-complétude ; la question « P = NP ? » reste ouverte.
  11. 1977Rivest, Shamir et Adleman publient le chiffrement RSA.
  12. 1998Sergey Brin et Larry Page présentent PageRank, cœur du moteur Google.

Compter avant de programmer

La complexité se mesure en nombre d'opérations selon la taille n. Entre log n, n log n, n² et 2n, l'écart dépasse vite ce que peut rattraper n'importe quelle machine.

Diviser pour régner

Couper le problème en deux — dichotomie, tri fusion, exponentiation rapide — fait apparaître un log₂ n ; retenir ce qu'on a déjà calculé évite les explosions de la récursivité.

Des algorithmes partout

Compression des fichiers, classement des pages du Web, sécurité des paiements : chaque fois, une idée simple, une complexité maîtrisée et des contrôles qui prouvent que le résultat est juste.

Pour aller plus loin
  • Le plus court chemin (Dijkstra, Bellman-Ford, Floyd-Warshall) : page le plus court chemin.
  • Les problèmes où l'on ne connaît pas d'algorithme rapide : le voyageur de commerce et le sac à dos.
  • Thèmes non traités ici : les tables de hachage (retrouver une donnée en temps presque constant), les graphes, la machine de Turing et la calculabilité, la question P = NP.
  • Les chiffres de cette page sont tous calculés dans le navigateur, puis confrontés à une formule exacte ou à une méthode indépendante.