Commit 67feca96 authored by Pierre Letouzey's avatar Pierre Letouzey
Browse files

improved solutions for tree2list

parent c265452e
......@@ -391,9 +391,9 @@ let rec map f l =
| x::q -> f x :: map f q
```
A régarder également dans la documentation : les itérateurs `fold_left` et `fold_right`.
A regarder également dans la documentation : les itérateurs `fold_left` et `fold_right`.
Attention à l'usage sur de grosses listes (plus de 30000 éléments), certaines fonctions peuvent échouer avec l'erreur `Stack overflow`. Pour éviter cela, on peut utiliser le style *récursif terminal* (cf p.ex. `rev_append` ci-dessus) ... ou bien utiliser autre chose que des listes (arbres ou tableaux).
Attention à l'usage sur de grosses listes (plus de 30000 éléments), certaines fonctions peuvent échouer avec l'erreur `Stack overflow`. Pour éviter cela, on peut utiliser le style *récursif terminal* (ou *tail-rec*, cf p.ex. `rev_append` ci-dessus) ... ou bien utiliser autre chose que des listes (arbres ou tableaux).
## Type de données : les arbres
......@@ -428,6 +428,57 @@ let rec tolist = function
| Noeud (x,g,d) -> tolist g @ [x] @ tolist d
```
Note: cette fonction `tolist` est simple et déjà fort satisfaisante, mais l'utilisation de `@` donne une complexité totale de `O(n.log(n))` pour un arbre de n noeuds, ce qui est correct, mais peut être rendu linéaire. De plus cette version n'est pas "tail recursive", et donnera `Stack Overflow` sur des gros arbres. Voici quelques autres réponses possibles à cette question.
```ocaml
(** Utilisation d'un accumulateur. Solution lineaire mais qu'à demi tail-rec.
Ceci dit ici la pile d'exécution est ici proportionnelle à la profondeur
de l'arbre, et non plus à sa taille, donc marchera très bien en pratique.
*)
let rec tolist_a t acc = match t with
| Leaf -> acc
| Node (x,g,d) -> tolist_a g (x :: tolist_a d acc)
let tolist t = tolist_a t []
(** Style par continuation. Solution tail-rec mais O(n.lg(n)).
See https://en.wikipedia.org/wiki/Continuation-passing_style *)
let rec tolist_cont t k = match t with
| Leaf -> k []
| Node (x,g,d) ->
tolist_cont d (fun ld ->
tolist_cont g (fun lg ->
k (lg @ x :: ld)))
let tolist t = tolist_cont t (fun l -> l)
(** Accumulateur + continuation. Solution tail-rec et linéaire.
let rec tolist_ac t acc k = match t with
| Leaf -> k acc
| Node (x,g,d) ->
tolist_ac d acc (fun ld ->
tolist_ac g (x::ld) k)
let tolist t = tolist_ac t [] (fun l -> l)
(** Avec une liste d'attente de sous-arbres à traiter (et un accumulateur).
Solution tail-rec, linéaire et sans fonctions locales.
Note : la liste d'attente va contenir des franges droites de l'arbre. *)
let rec tolist_nxt t next acc = match t with
| Leaf ->
(match next with
| [] -> acc
| (x,g)::next -> tolist_nxt g next (x::acc))
| Node (x,g,d) -> tolist_nxt d ((x,g)::next) acc
let tolist3 t = tolist_nxt t [] []
```
L'intérêt de ce genre d'arbre vient par exemple quand les données sont rangées par ordre croissant lors d'un parcours infixe, on parle alors d'arbre binaire de recherche (ABR).
```ocaml
......@@ -443,7 +494,7 @@ let rec estabr = function
pourtout (fun y -> y<x) g && pourtout (fun y -> y>x) d && estabr g && estabr d
```
Note: cette version de la fonction `estabr` a une mauvaise complexité. Exercice : en écrire une version linéaire.
Note: en comptant le déroulement des appels aux `pourtout`, cette version de la fonction `estabr` demande de multiples passages dans l'arbre, un peu plus à chaque niveau. Et la complexité totale est `O(n.log(n))` pour un arbre de n noeuds, ce qui est correct, mais améliorable. Exercice : écrire une version linéaire procédant en une passe unique. Indice: utiliser des bornes inférieures et supérieures, éventuellement inconnues au début (cf. le type `option`).
On peut alors faire de la recherche dichotomique pour savoir si un élément est dans l'arbre ou non:
```ocaml
......
......@@ -255,7 +255,7 @@ La tête de la liste (sa gauche) s'accède ou se ralonge en temps constant. Par
A regarder également dans la documentation : les itérateurs `fold_left` et `fold_right`.
Attention à l'usage sur de grosses listes (plus de 30000 éléments), certaines fonctions peuvent échouer avec l'erreur `Stack overflow`. Pour éviter cela, on peut utiliser le style *récursif terminal* (cf p.ex. `rev_append` ci-dessus) ... ou bien utiliser autre chose que des listes (arbres ou tableaux).
Attention à l'usage sur de grosses listes (plus de 30000 éléments), certaines fonctions peuvent échouer avec l'erreur `Stack overflow`. Pour éviter cela, on peut utiliser le style *récursif terminal* (ou *tail-rec*, cf p.ex. `rev_append` ci-dessus) ... ou bien utiliser autre chose que des listes (arbres ou tableaux).
## Type de données : les arbres
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment