Algorithmie avancée — fiche de révision

Suppression dans les arbres, tas binaires, files de priorité et mémoïsation.

« If you can solve it, it's an exercise, otherwise it's a research problem » (Richard Bellman, inventeur de la programmation dynamique). Cette suite approfondit les structures arborescentes et introduit la mémoïsation, porte d'entrée vers la programmation fonctionnelle.

Définitions à savoir

Arbres & structures

Arbrestructure de données abstraite composée de nœuds atteignables par un seul chemin.
Arbre binairearbre enraciné dont les nœuds n'admettent au plus que deux branches.
Racinel'unique point d'entrée d'un arbre.
Nœud / branche / feuilleun nœud est une valeur reliée à d'autres ; une branche est la relation entre nœuds ; une feuille est un nœud sans descendance.
Hiérarchiquestructuré en échelons, formant un chemin unique pour atteindre un nœud.
Arbre binaire de recherchearbre binaire avec relation d'ordre fils gauche < parent < fils droit.
Tas binaire (heap)arbre binaire hiérarchique avec une relation d'ordre parent > fils, sans ordre entre les fils.
File de prioritéune collection qui rend la valeur la plus prioritaire.
Topologieclassification établie déductivement (à partir de propriétés).
Taxonomieclassification établie empiriquement (à partir de cas observés).

Mémoïsation & concepts

Transparence référentielleaptitude d'une expression à être remplacée par sa valeur sans changer le comportement du logiciel. (Quine, 1960.)
Fonction purefonction déterministe sans effet de bord. La pureté implique la transparence référentielle.
Cacheun dictionnaire utilisé pour mémoïser. En retirer les données périmées (stale data) = invalider le cache.

Arbres binaires de recherche : la suppression

Pour traiter la suppression rigoureusement, on construit une topologie des cas (déductive), et non une simple taxonomie (empirique). On croise deux paramètres : la valeur à supprimer et la forme de l'arbre.

Cas sur la valeur : dégénérée · absente · présente — et si présente : à la racine, à une feuille, ou au milieu.

Cas sur la forme de l'arbre, du plus simple au plus général : arbre vide → racine seule → racine + une feuille (supérieure / inférieure) → racine + deux feuilles → racine + sous-arbre supérieur de hauteur 1 (avec / sans feuille inférieure) → idem côté inférieur → puis sous-arbre de hauteur arbitraire. On filtre ensuite les cas qui se ramènent concrètement à un seul.

Les tas binaires

30 25 20 18 22 12 15 max à la racine
Tas max : chaque parent ≥ ses fils, mais aucun ordre entre frères (ici 22 > 20 bien que 20 soit ailleurs). À ne pas confondre avec l'ABR (gauche < parent < droite).

Le tas garde donc le plus prioritaire à la racine, accessible immédiatement. Insertion et retrait se font en O(log n) — c'est optimal pour récupérer les « K plus grands ». Revers : la structure est peu compatible avec le cache (cache-unfriendly) car les nœuds reliés sont éloignés en mémoire.

Les files de priorité

Une file de priorité rend toujours la valeur la plus prioritaire (et non la première ou la dernière insérée — l'ordre d'insertion est une variable non descriptive). Elle s'implémente naturellement avec un tas binaire.

Invariants du contrat

Méthodes & cas

La mémoïsation

Cas typique : un service de facturation appelle souvent une API externe pour obtenir le prix d'un médicament. Chaque requête a un coût financier (facturée) et temporel (latence réseau : encodage + réseau + traitement + réseau + décodage). Or le « cap » de l'ingénieur, c'est d'obtenir O(1) sans coût.

L'appel getPrix("ibuprofène") → 2,57 € est une relation (clé, valeur) : on peut donc remplacer l'appel par un dictionnaire, c'est-à-dire remplacer dynamiquement la procédure par sa valeur de retour. C'est la transparence référentielle.

Client Cache (dictionnaire) API externe getPrix(…) 2,57 € en O(1) 1er appel : coût € prix (mis en cache)
Premier appel : le cache interroge l'API (coûteux) puis mémorise. Appels suivants : réponse directe depuis le cache, en O(1) et sans coût.

Condition 1 — le cycle de vie de la donnée

L'équivalence n'est valable que pendant la durée de stabilité de la valeur :

Type de donnéeComportementStratégie
Invariantene change jamaismise en cache permanente
Stable mais périssablechange à fréquence connueexpiration automatique (TTL)
Volatilefréquence indéterminéeinvalidation explicite

Condition 2 — la fonction elle-même

Pour être mémoïsable, l'appel doit être une fonction (avoir une valeur de retour), sans effet de bord et sans dépendre d'un état mutable externe (sinon, il faut capturer cet état comme paramètre). Une telle fonction est pure : aucune invalidation n'est alors nécessaire (ex. calculs scientifiques).

Exemple concret côté web : un serveur HTTP autorise le client à mettre en cache ses réponses via les codes 304 / 200 et l'en-tête ETag (identifiant de la ressource + de sa version).

Les grands noms (et pourquoi)

NomPourquoi il est cité
Richard Bellmaninventeur de la programmation dynamique — dont la mémoïsation est la porte d'entrée.
W. V. O. Quine (1960)formalise la transparence référentielle et la fonction pure — fondements de la programmation fonctionnelle.
Bertrand Russell (1910)les travaux logiques (Principia Mathematica) sur lesquels Quine s'appuie.
Niklaus Wirth« Algorithms + Data Structures = Programs » : on choisit la structure selon le contrat visé.

À retenir : le tas garde le maximum à la racine (O(log n)) et sert de socle à la file de priorité ; la mémoïsation échange un appel coûteux contre un accès O(1) dans un cache — valable tant que la fonction est pure (ou que l'on gère le cycle de vie de la donnée).

D'après le cours d'algorithmie avancée (A. Roger, ENSICAEN). Fiche de révision personnelle.