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)->tolistg@[x]@tolistd
```
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.
*)
letrectolist_atacc=matchtwith
|Leaf->acc
|Node(x,g,d)->tolist_ag(x::tolist_adacc)
lettolistt=tolist_at[]
(** Style par continuation. Solution tail-rec mais O(n.lg(n)).
See https://en.wikipedia.org/wiki/Continuation-passing_style *)
letrectolist_conttk=matchtwith
|Leaf->k[]
|Node(x,g,d)->
tolist_contd(funld->
tolist_contg(funlg->
k(lg@x::ld)))
lettolistt=tolist_contt(funl->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).
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:
@@ -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).