Probleme – De l'arbre à la pile : évaluer une expression
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 10 — Arbres
Énoncé
Une expression arithmétique est l'ensemble inductif engendré par les constantes et les deux règles et .
- Écrire l'évaluation directe sur l'arbre.
- Écrire la production de la suite postfixée des jetons.
- Écrire l'évaluation d'une suite postfixée par une pile, et prouver qu'elle donne le même résultat.
- Quelle hauteur de pile faut-il prévoir ?
Corrigé
1. L'évaluation sur l'arbre est le cas d'école de l'induction : la structure de la fonction est la structure du type.
type expr = Cst of int | Plus of expr * expr | Fois of expr * expr
(* Valeur de e. Terminaison : ordre induit. Complexite : Theta(n). *)
let rec evalue = function
| Cst n -> n
| Plus (a, b) -> evalue a + evalue b
| Fois (a, b) -> evalue a * evalue b
2. La suite postfixée.
type jeton = J of int | Op of char
(* Suite postfixee des jetons de e : les operandes AVANT l'operateur. *)
let rec postfixe = function
| Cst n -> [J n]
| Plus (a, b) -> postfixe a @ postfixe b @ [Op '+']
| Fois (a, b) -> postfixe a @ postfixe b @ [Op '*']
Mesures : donne 2 3 4 <em> + et vaut ; donne 2 3 + 4 </em> et vaut . Les deux suites contiennent les mêmes jetons dans un ordre différent, et aucune parenthèse n'y figure : la priorité est portée par l'ordre. C'est ce qui fait l'intérêt de la notation postfixée.
3. L'évaluation par une pile.
(* Valeur de la suite postfixee l.
Precondition : l provient de `postfixe` appliquee a une expression.
Complexite : Theta(|l|) en temps, O(hauteur de pile) en memoire. *)
let evalue_postfixe l =
let pile = List.fold_left (fun p j -> match j, p with
| J n, p -> n :: p (* on EMPILE *)
| Op c, y :: x :: r -> (if c = '+' then x + y else x * y) :: r
| Op _, _ -> failwith "pile trop courte") [] l in
match pile with
| [v] -> v
| _ -> failwith "pile finale de taille differente de 1"
Noter l'ordre y :: x :: r et le calcul x + y : le sommet de pile est le second opérande, puisqu'il a été empilé en dernier. Sur l'addition et la multiplication cela ne se voit pas ; sur une soustraction, l'inversion serait une faute silencieuse.
<details class="group my-6 border border-gray-300 rounded-2xl bg-black/[0.03] overflow-hidden transition-all duration-300"><summary style="color:#1e3a8a" class="flex items-center justify-between p-4 cursor-pointer text-xs font-bold select-none"><div class="flex items-center"><i class="fa-solid fa-key mr-2"></i>Démonstration (que les deux évaluations coïncident)</div><span class="transition-transform group-open:rotate-180"><i class="fa-solid fa-chevron-down"></i></span></summary><div style="color:#1d4ed8" class="force-blue p-4 pt-0 border-t border-gray-200 bg-black/[0.02] leading-relaxed font-sans text-xs select-text"> On démontre l'énoncé renforcé, par induction structurelle sur :
où est le fold_left partant de la pile .
Cas . La suite est : on empile , et l'on obtient .
Cas . La suite est . Par hypothèse d'induction sur avec la pile , on arrive à . Par hypothèse d'induction sur avec cette nouvelle pile, on arrive à . Le jeton dépile les deux et empile leur somme : .
Cas : identique.
Là encore, c'est la quantification « pour toute pile » qui fait passer l'induction — exactement comme la quantification sur dans le problème précédent. Sans elle, l'hypothèse ne s'applique pas au second sous-arbre, dont l'évaluation commence sur une pile non vide.
Vérification : sur expressions tirées au hasard jusqu'à la profondeur , à chaque fois.
4. La hauteur de pile. Elle vaut exactement (nombre de jetons empilés en attente), et l'on montre par induction que la hauteur maximale atteinte en évaluant à partir d'une pile vide est
Le devant dit que la valeur de occupe une case pendant toute l'évaluation de . Il en résulte , où est la hauteur de l'arbre. Mesure : sur les expressions de profondeur , la plus grande hauteur de pile observée est , ce qui sature la borne.
Le fait remarquable qu'on peut en tirer. La formule n'est pas symétrique : évaluer d'abord le sous-arbre le plus profond économise de la pile. C'est le nombre d'Ershov, et c'est ce qu'un vrai compilateur calcule pour allouer ses registres : évaluer le sous-arbre le plus exigeant en premier minimise le nombre de registres occupés simultanément.
Ce que ce problème boucle. Un arbre, sa forme aplatie, une pile, et le tout qui redonne la même valeur : c'est le lien annoncé par le cours entre les parcours et l'empilement des blocs d'activation. La pile de evalue_postfixe est, à peu de chose près, celle qu'aurait construite la récursion de evalue.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.