Informatique

Les algorithmes : des recettes pour les machines

Qu'est-ce qu'un algorithme, comment le juge-t-on, et pourquoi toute l'informatique repose sur lui ? Cinq planches à manipuler, d'Euclide à la machine de Turing ; tous les chiffres sont mesurés par la page elle-même.

Qu'est-ce qu'un algorithme ?

En une phrase : un algorithme est une suite finie d'instructions précises qui, appliquée à des données (les entrées), produit un résultat (la sortie) en un nombre fini d'étapes, sans qu'il soit jamais nécessaire de réfléchir ou d'improviser.

Le schéma de tout algorithme : des entrées quelconques (ici des nombres dans le désordre) entrent, une procédure mécanique les traite, une sortie conforme (ici la liste rangée) ressort. La même procédure marche pour toutes les entrées.

Une recette de cuisine, la technique de la division posée apprise à l'école ou le mode d'emploi d'un meuble en kit sont déjà des algorithmes. Le mot vient du mathématicien persan al-Khwârizmî (vers 780-850), dont le nom latinisé en Algoritmi désignait au Moyen Âge les méthodes de calcul en chiffres indo-arabes. Le mathématicien Donald Knuth résume les qualités exigées d'un algorithme en cinq mots :

Finitudeil s'arrête toujours après un nombre fini d'étapes.
Précisionchaque instruction n'a qu'un seul sens possible.
Entréesil reçoit des données, choisies dans un ensemble défini.
Sortiesil fournit un résultat lié aux entrées.
Effectivitéchaque étape est assez simple pour être faite « à la main ».

Algorithme et programme : l'idée et sa traduction

Un programme est la traduction d'un algorithme dans un langage que la machine comprend. Le même algorithme, celui d'Euclide pour le plus grand commun diviseur (étudié en planche 1), s'écrit ainsi en pseudo-code, en Python et en JavaScript : les mots changent, la méthode est identique.

// pseudo-code
fonction PGCD(a, b)
  tant que b ≠ 0 :
    r ← reste de a ÷ b
    a ← b
    b ← r
  renvoyer a
# Python
def pgcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a
// JavaScript
function pgcd(a, b) {
  while (b !== 0) {
    [a, b] = [b, a % b];
  }
  return a;
}

Où se cachent les algorithmes ?

Chaque geste quotidien devant un écran déclenche des algorithmes, souvent plusieurs centaines. Quelques exemples :

UsageProblème à résoudreAlgorithmeIdée en une phrase
GPS, itinéraireplus court chemin dans un réseauDijkstra, A*on fixe les villes une à une, de la plus proche à la plus lointaine.
Moteur de rechercheclasser des milliards de pagesPageRankune page est importante si des pages importantes pointent vers elle.
Paiement en lignechiffrer un messageRSA, AESmultiplier deux grands nombres premiers est facile, retrouver les facteurs est très long.
Fichier ZIPcompresser sans perteHuffman, LZ77codes courts pour les lettres fréquentes, renvois vers les répétitions déjà vues.
Photo, musiquecompresser avec perteJPEG, MP3on décompose en fréquences et on jette celles que l'œil ou l'oreille ne perçoivent pas.
Tableur, base de donnéestrier, retrouver une lignetri fusion, arbres Bdiviser pour régner (planches 2 et 3).
Correcteur d'orthographemot le plus prochedistance de Levenshteincompter le minimum de lettres à ajouter, ôter ou changer.
Intelligence artificiellerégler des millions de paramètresdescente de gradientdescendre pas à pas dans la direction où l'erreur diminue le plus.
1

Le plus ancien algorithme : Euclide et le PGCD

Question : à quoi ressemble un algorithme, concrètement, quand on le regarde travailler ? Euclide (vers −300) cherche le plus grand commun diviseur (PGCD) de deux entiers, le plus grand nombre qui les divise tous deux. Son idée : le PGCD de a et b est aussi celui de b et du reste de a÷b. On recommence jusqu'à un reste nul. Géométriquement, on découpe un rectangle a×b en carrés les plus grands possible ; le dernier carré a pour côté le PGCD.

Chaque couleur est une étape : on y place autant de carrés de côté b que possible (le quotient q), il reste un rectangle plus petit. En rouge, le pavage final par des carrés de côté PGCD.
fonction PGCD(a, b)  tant que b ≠ 0 :    r ← reste de a ÷ b    a ← b    b ← r  renvoyer a
étapeabquotient qreste r
PGCD par Euclide
—
divisions effectuées
—
méthode naïve
—
essais de tous les diviseurs, du plus petit des deux nombres vers 1
contrôle
—
les deux méthodes donnent-elles le même PGCD ?
Trois idées sont déjà là. L'algorithme est général : les mêmes cinq lignes servent pour tous les couples. Il se termine : le reste diminue strictement, il finit donc par valoir 0. Deux méthodes justes ne se valent pas :
2

Trier : quatre algorithmes pour un même problème

Question : si plusieurs algorithmes donnent tous la bonne réponse, lequel choisir ? Trier une liste est le problème le plus étudié de l'informatique. On compte ici les comparaisons (« cet élément est-il plus grand que celui-là ? »), l'opération de base d'un tri. La course est équitable : à chaque image, chaque algorithme a droit au même nombre de comparaisons.

Hauteur d'une barre = valeur à ranger. En rouge, les deux éléments comparés à l'instant ; en orange, l'élément qu'on vient de déplacer. Bulles : on échange les voisins mal rangés. Insertion : on glisse chaque élément à sa place parmi ceux déjà triés, comme des cartes en main. Fusion : on coupe en deux, on trie chaque moitié, on fusionne. Rapide : on choisit un pivot (ici le dernier élément), on met les petits à gauche, les grands à droite, on recommence de chaque côté.
bulles
—
insertion
—
fusion
—
rapide
—
repères
—
n(n−1)/2 et n·log₂ n
contrôle
—
résultat comparé au tri du navigateur
Les quatre tris finissent avec la même liste, mais pas au même prix. Bulles et insertion font de l'ordre de n²/2 comparaisons au pire ; fusion reste toujours près de n·log₂ n. Choisissez « déjà trié » : l'insertion devient imbattable (n−1 comparaisons) et le tri rapide, avec ce pivot naïf, s'effondre — l'efficacité d'un algorithme dépend aussi des données.
3

Chercher : la puissance de la dichotomie

Question : combien de questions faut-il pour retrouver un nom dans un annuaire ? La recherche séquentielle regarde les cases une à une. La recherche dichotomique (du grec « couper en deux ») exige une liste triée : elle regarde la case du milieu et élimine d'un coup la moitié qui ne peut pas contenir la cible. Chaque question divise le travail restant par deux.

Cible encadrée en vert. En haut, la recherche séquentielle grise les cases déjà examinées. En bas, la dichotomie grise les moitiés éliminées ; le crochet bleu délimite la zone encore possible, la case rouge est celle du milieu que l'on interroge.
séquentielle
—
questions posées
dichotomie
—
questions posées
borne théorique
—
log₂(n+1) questions au plus
contrôle exhaustif
—
moyennes mesurées
—
Quand la liste double de taille, la séquentielle double son travail, la dichotomie n'ajoute qu'une seule question. C'est le principe des index de bases de données, des dictionnaires et des tables de mots des moteurs de recherche : ranger d'abord (planche 2) pour trouver vite ensuite.
4

La complexité : pourquoi la vitesse des machines ne suffit pas

Question : une machine mille fois plus rapide résout-elle des problèmes mille fois plus grands ? La complexité d'un algorithme est la façon dont son nombre d'opérations grandit avec la taille n des données. On la note par sa forme dominante : log n, n, n·log n, n², 2ⁿ, n!… Sur ce graphique à échelles logarithmiques, chaque graduation multiplie par 10.

CourbesCourbes : nombre d'opérations selon n pour sept complexités.nbsp;: nombre d'opérations (axe vertical) selon la taille n des données (axe horizontal) pour sept complexités. Ligne rouge : le budget, c'est-à-dire ce que la machine choisie peut faire dans la durée choisie. Points : comparaisons réellement comptées par la page pour le tri par insertion (violet) et le tri fusion (vert), sur des listes au hasard.
pente mesurée, insertion
—
pente mesurée, fusion
—
machine 1 000 fois plus rapide
—
gain sur la taille traitable
Sur une échelle log-log, une complexité en np devient une droite de pente p : la page mesure pour l'insertion et pour la fusion. Un bon algorithme bat une machine rapide : multiplier la vitesse par 1 000 multiplie la taille traitable par 1 000 pour n, par 31,6 pour n², mais n'ajoute qu'environ 10 éléments pour 2ⁿ. Les problèmes en 2ⁿ ou n! (voyageur de commerce par énumération, par exemple) restent hors d'atteinte quelle que soit la machine.
5

Les limites : ce qu'un algorithme ne peut pas faire

Question : tout problème bien posé a-t-il un algorithme ? En 1936, Alan Turing répond par la négative. Pour cela il lui faut d'abord définir ce qu'est un algorithme : c'est sa machine, un ruban de cases, une tête qui lit et écrit, et une table de règles. Tout ce que fait un ordinateur, une machine de Turing peut le faire.

Règle de fonctionnement : dans l'état courant, la tête lit la case ; la table dit quoi écrire, de quel côté se déplacer (→ droite, ← gauche) et dans quel état passer. La machine s'arrête en atteignant l'état « Arrêt ». Les « castors affairés » sont les machines à n états qui écrivent le plus de 1 avant de s'arrêter, sur un ruban vide au départ.

Le ruban, la tête (triangle rouge) et son état. La case lue est encadrée.
pas effectués
0
état
A
nombre de 1 sur le ruban
0
contrôle
—

Un algorithme de quatre lignes : partir d'un entier n ; s'il est pair, le diviser par 2, sinon le remplacer par 3n+1 ; recommencer jusqu'à tomber sur 1. Tout le monde sait l'écrire, personne ne sait démontrer qu'il s'arrête toujours. C'est la conjecture de Syracuse (ou de Collatz), vérifiée par ordinateur bien au-delà de 10²⁰ mais toujours ouverte.

La trajectoire de n (échelle verticale logarithmique) : le « vol » monte, retombe, remonte, puis finit sur le cycle 4, 2, 1.
Durée du vol (nombre d'étapes) pour chaque départ de 1 à 10 000. Aucune formule ne prédit ce nuage ; le point rouge est le départ choisi.
durée du vol
—
étapes avant d'atteindre 1
altitude maximale
—
record de 1 à 10 000
—
contrôle
—
tous les départs testés atteignent 1

Supposons qu'il existe un programme ARRÊT(P, x) qui, pour tout programme P et toute entrée x, réponde à coup sûr « P s'arrête sur x » ou « P boucle sans fin ». On pourrait alors remplir le tableau ci-dessous pour tous les programmes, en donnant à chacun son propre texte comme entrée. Construisons le programme D : sur l'entrée Pi, D consulte la case diagonale (i, i) et fait le contraire : il boucle si Pi s'arrête, il s'arrête si Pi boucle.

Lignes : les programmes P₁, P₂, … ; colonnes : l'entrée donnée (le texte de P₁, P₂, …). Vert ✓ = s'arrête, orange ∞ = boucle. La dernière ligne est D, obtenue en inversant la diagonale.
D diffère de chaque ligne
—
au moins une case différente, sur la diagonale
D est-il dans le tableau ?
—

D est un programme, il devrait donc figurer quelque part dans la liste de tous les programmes, disons à la ligne d. Mais D diffère de Pd précisément sur la case (d, d) : D s'arrête sur son propre texte si et seulement si il ne s'arrête pas. Contradiction : le programme ARRÊT ne peut pas exister.

Trois limites, de natures différentes. Les castors affairés grandissent plus vite que toute fonction calculable : Syracuse montre qu'un algorithme minuscule peut résister à toutes les mathématiques connues. Et l'argument de la diagonale prouve qu'aucun algorithme ne peut décider, pour tout programme, s'il finira par s'arrêter : c'est pour cela qu'aucun logiciel ne détectera jamais tous les bogues d'un autre.

Quatre mille ans d'algorithmes

En résumé

Une méthode, pas un calcul

Un algorithme est une procédure finie, précise et mécanique, valable pour toutes les entrées d'un problème. Un programme en est la traduction dans un langage. Sa justesse se démontre : il s'arrête, et ce qu'il renvoie est bien la réponse.

Le coût compte autant que la justesse

Plusieurs algorithmes justes peuvent différer d'un facteur mille ou d'un milliard. La complexité, mesurée en ordre de grandeur (n, n log n, n², 2ⁿ), décide de ce qui est faisable ; diviser pour régner (dichotomie, tri fusion) est l'idée la plus féconde.

Il existe des limites absolues

Certains problèmes n'ont aucun algorithme (l'arrêt des programmes), d'autres en ont mais trop lents pour l'Univers entier. Comprendre où passent ces frontières, c'est l'objet de l'informatique théorique.

Pour aller plus loin
  • P = NP ? Pour de nombreux problèmes (emploi du temps, voyageur de commerce, sac à dos), vérifier une solution est rapide mais on ne connaît aucun moyen rapide d'en trouver une. Savoir si c'est inévitable est la grande question ouverte de l'informatique.
  • Algorithmes probabilistes. Tirer au hasard peut accélérer beaucoup : le test de primalité de Miller-Rabin, utilisé pour fabriquer les clés RSA, se trompe avec une probabilité aussi petite qu'on veut.
  • Borne des tris par comparaison. Aucun tri fondé sur des comparaisons ne peut faire mieux que log₂(n!) ≈ n·log₂ n comparaisons au pire : le tri fusion est donc optimal à un facteur près.
  • Thèse de Church-Turing. Tous les modèles de calcul raisonnables (machines de Turing, λ-calcul, ordinateurs réels) calculent exactement les mêmes fonctions : la notion d'algorithme ne dépend pas de la machine.
  • Lecture. Donald Knuth, The Art of Computer Programming ; Thomas Cormen et coll., Algorithmique (Dunod) ; Gilles Dowek, Les métamorphoses du calcul.