Adloun

informatique (MP2I/MPI) · Chapitre 10 · Exercice de TD

Problème — Écrire un arbre sur une ligne, et le relire

Arbres · niveau 3 (difficile)

Énoncé

On veut ranger un arbre dans un fichier, puis le reconstruire à l'identique.

  • Pourquoi le parcours préfixe seul ne suffit-il pas ?
  • Définir une sérialisation préfixe avec marqueurs, et écrire les deux fonctions.
  • Prouver que la relecture est l'inverse exact de l'écriture.
  • Combien de jetons l'écriture produit-elle ? Quel en est le coût ?

Corrigé

1. Le préfixe seul est ambigu, et l'exercice 10.9 en donne le contre-exemple : les deux arbres à deux nœuds où est fils gauche ou fils droit de ont le même préfixe . Il manque exactement l'information « ce fils-ci est vide ».

2. La sérialisation avec marqueurs. On écrit un jeton pour chaque appel récursif, y compris sur les arbres vides.


(* Renvoie la liste des jetons du parcours prefixe de a, ou chaque arbre
   vide produit le jeton ".". Complexite : Theta(n). *)
let rec serialise = function
  | Vide -> ["."]
  | Noeud (g, e, d) -> string_of_int e :: (serialise g @ serialise d)

(* Reconstruit l'arbre a partir de ses jetons.
   Precondition : la liste provient de `serialise`.
   Leve une exception si les jetons manquent ou sont en trop. *)
let deserialise jetons =
  let rec aux = function
    | [] -> failwith "jetons manquants"
    | "." :: r -> (Vide, r)
    | x :: r ->
        let (g, r1) = aux r in                (* le sous-arbre GAUCHE d'abord *)
        let (d, r2) = aux r1 in               (* puis le DROIT, sur ce qui reste *)
        (Noeud (g, int_of_string x, d), r2)
  in
  let (a, reste) = aux jetons in
  if reste <> [] then failwith "jetons en trop" else a

Mesure : sur l'arbre de l'exercice 10.1, on obtient les quinze jetons


8 3 1 . . 6 4 . . . 9 7 . . .

et deserialise (serialise a) = a est vérifié.

La clé de deserialise, et c'est ce qui rend le problème intéressant : la fonction auxiliaire rend un couple — l'arbre lu, et ce qui reste à lire. Sans ce second membre, l'appel sur le sous-arbre droit ne saurait pas où commencer. C'est le même procédé que l'accumulateur, à l'envers : au lieu de porter ce qui est construit, on porte ce qui reste.

3. La preuve que . On démontre l'énoncé renforcé, sans lequel l'induction ne passe pas :

Démonstration

Par induction structurelle sur .

Cas . , et aux rend .

Cas . Alors

La fonction lit , puis appelle aux sur . L'hypothèse d'induction sur , appliquée avec la liste , donne le couple . Le second appel, par hypothèse d'induction sur avec la liste , donne . On reconstruit donc , en laissant .

L'énoncé renforcé est indispensable : la propriété « » ne s'hérite pas, car dans le cas récursif l'appel sur ne porte pas sur une liste vide. C'est la difficulté du problème, et elle est de méthode : quand une induction échoue, il faut souvent prouver plus.

En prenant , on obtient , donc deserialise ne lève pas l'exception et rend .

4. Le nombre de jetons est . Mesure : jetons pour . La preuve est une induction immédiate : donne , et donne .

Autrement dit : un arbre à nœuds a exactement sous-arbres vides. C'est le même comptage que les intervalles délimités par points, et il resservira au chapitre chap:tas.

Coût. La désérialisation est en : chaque jeton est consommé une fois. La sérialisation, telle qu'écrite, est en sur un peigne à cause du @ — exercice 10.5. Un accumulateur la ramène à :


let rec serialise_acc a acc = match a with
  | Vide -> "." :: acc
  | Noeud (g, e, d) -> string_of_int e :: serialise_acc g (serialise_acc d acc)

Ce que le problème apprend. On a mesuré le prix de la réversibilité : jetons de plus, soit un peu plus du double. C'est le prix universel de la sérialisation d'un arbre — et il explique pourquoi les formats réels préfèrent souvent écrire, à la place, le nombre de fils de chaque nœud : même quantité d'information, autre découpage.

Exercices semblables

Le cours du chapitre · Tous les exercices du chapitre