TD n° 6 : Arbres

In [2]:
type ('f, 'n) arbre =
    | Feuille of 'f
    | NoeudInterne of ('f, 'n) arbre * 'n * ('f, 'n) arbre
;;
Out[2]:
type ('f, 'n) arbre =
    Feuille of 'f
  | NoeudInterne of ('f, 'n) arbre * 'n * ('f, 'n) arbre

Exercice 1

In [3]:
let arbre =
  NoeudInterne (Feuille 2, 1, NoeudInterne (Feuille 4, 3, Feuille 5))
;;
Out[3]:
val arbre : (int, int) arbre =
  NoeudInterne (Feuille 2, 1, NoeudInterne (Feuille 4, 3, Feuille 5))

Exercice 2

In [4]:
let rec symetrique arbre =
  match arbre with
  | Feuille _ -> arbre
  | NoeudInterne (fils_gauche, noeud, fils_droit) ->
    NoeudInterne (symetrique fils_droit, noeud, symetrique fils_gauche)
;;
Out[4]:
val symetrique : ('a, 'b) arbre -> ('a, 'b) arbre = <fun>
In [5]:
symetrique arbre;;
Out[5]:
- : (int, int) arbre =
NoeudInterne (NoeudInterne (Feuille 5, 3, Feuille 4), 1, Feuille 2)

Exercice 3

1)

In [6]:
let rec initialise_complet n val_f val_n =
  match n with
  | 0 -> Feuille val_f
  | _ -> let arbre = initialise_complet (n - 1) val_f val_n in
    NoeudInterne (arbre, val_n, arbre)
;;
Out[6]:
val initialise_complet : int -> 'a -> 'b -> ('a, 'b) arbre = <fun>
In [7]:
initialise_complet 2 1 0;;
Out[7]:
- : (int, int) arbre =
NoeudInterne (NoeudInterne (Feuille 1, 0, Feuille 1), 0,
 NoeudInterne (Feuille 1, 0, Feuille 1))

2) Un arbre complet de hauteur $h$ a $2^h$ feuilles de profondeur $h$ chacune. Son cheminement est donc de $h\cdot 2^h$.

Exercice 4

1)

In [8]:
let rec strahler arbre =
    match arbre with
    | Feuille _ -> 1
    | NoeudInterne(fils_gauche, _, fils_droit) ->
        let s_g = strahler fils_gauche in
        let s_d = strahler fils_droit in
        if s_g = s_d then
            s_g + 1
        else
            max s_g s_d
;;
Out[8]:
val strahler : ('a, 'b) arbre -> int = <fun>
In [9]:
let arbre2 = 
    let sous_arbre = initialise_complet 2 0 0 in
    NoeudInterne(
        NoeudInterne(
            sous_arbre,
            0,
            NoeudInterne(
                sous_arbre,
                0,
                Feuille 0
                )
            ),
        0, 
        Feuille 0
        )
;;
Out[9]:
val arbre2 : (int, int) arbre =
  NoeudInterne
   (NoeudInterne
     (NoeudInterne (NoeudInterne (Feuille 0, 0, Feuille 0), 0,
       NoeudInterne (Feuille 0, 0, Feuille 0)),
     0,
     NoeudInterne
      (NoeudInterne (NoeudInterne (Feuille 0, 0, Feuille 0), 0,
        NoeudInterne (Feuille 0, 0, Feuille 0)),
      0, Feuille 0)),
   0, Feuille 0)
In [10]:
strahler arbre2;;
Out[10]:
- : int = 4

2) Par récurrence sur $h\in\mathbb N$.

  • Initialisation : Si on considère un arbre de hauteur $0$, la racine est une feuille et le nombre de Strahler est $1=h+1$ avec un arbre qui est bien complet.
  • Hérédité : Soit une hauteur $h\geqslant0$ pour laquelle le résultat est vrai jusqu'à $h$, et soit un arbre de hauteur $h+1$, en notant $s$ son nombre de Strahler, considérons $s_g$ et $s_d$ les nombres de Strahler des fils gauche et droit de la racine. Comme ce sont des arbres de hauteur au plus $h$, par hypothèse de récurrence, $s_g\leqslant h+1$ et $s_d\leqslant h+1$. Alors $s\in\{s_g + 1, max(s_g, s_d)\}$ donc $s\leqslant h + 2$. De plus, il y a égalité si et seulement si $s = s_g + 1$ si et seulement si $s_g = s_d = h + 1$ si et seulement si les fils gauche et droit sont complets de hauteur $h$ (par hypothèse de récurrence) si et seulement si l'arbre est complet, ce qui établit la récurrence.

3) Un arbre de hauteur $h\neq0$ possède au moins un noeud interne, donc au moins un noeud avec deux feuilles qui aura $2$ comme nombre de Strahler. Comme le nombre de Strahler d'un noeud est toujours au moins aussi grand que ceux de ses fils, un arbre de hauteur $h\neq0$ a bien un nombre de Strahler au moins égal à $2$.

De plus, ce nombre sera égal à $2$ si et seulement si tous les noeuds internes ont un nombre de Strahler de $2$ (toujours vu la propriété de croissance). Cela signifie que chaque noeud interne doit avoir un fils dont le nombre de Strahler vaut $1$ et un fils dont le nombre de Strahler vaut $2$, ce qui correspond bien au type d'arbre décrit dans la question.

Exercice 5

1) et 2) Notons que cette relation n'a de sens que pour un arbre non vide.

  • Un arbre possédant $n$ noeuds a une hauteur maximale lorsqu'il ne possède qu'un noeud par niveau. Il s'agit d'un arbre peigne de hauteur $n-1$ de la forme
        0
        |
        1
        |
        2
        |
       ...
        |
      n - 1
    

On a donc toujours $h\leqslant n-1$.

  • Un arbre possédant $n$ noeuds a une hauteur minimale lorsque chaque niveau est complet sauf éventuellement le dernier, donc de la forme :
         0
       /   \
      1     2
     / \   / 
    3  4  5 
    

On a alors $1+2+\dotsb+2^{h-1}<n\leqslant1+2+\dotsb+2^h$ soit $2^{h}-1<n\leqslant2^{h+1}-1$, donc $2^h\leqslant n<2^{h+1}$ soit $h\leqslant\log_2 n< h+1$ et donc $h=\lfloor\log_2 n\rfloor$.

On a donc toujours $h\geqslant\lfloor\log_2 n\rfloor$.

Autre preuve possible : par induction structurelle.

  • Pour un arbre n'ayant qu'un noeud (arbre feuille), on a $0\leqslant0\leqslant0$.
  • Si un arbre est un noeud ayant un seul fils non vide, et que la relation est vraie pour ce fils de hauteur $h_f=h-1$ et de taille $n_f=n-1$, alors $\lfloor\log_2{n_f}\rfloor+1\leqslant h=h_f+1\leqslant n_f=n-1$ donc $\lfloor\log_2{(n-1)}\rfloor+1\leqslant h\leqslant n-1$. Or $\lfloor\log_2{(n-1)}\rfloor+1=\lfloor\log_2{(2n-2)}\rfloor\geqslant\lfloor\log_2 n\rfloor$ car $n\geqslant2$. On a bien $\lfloor\log_2 n\rfloor\leqslant h\leqslant n-1$.
  • Si un arbre est un noeud ayant deux fils non vides, et que la relation est vraie pour les deux fils donc $\lfloor\log_2 n_g\rfloor\leqslant h_g\leqslant n_g-1$ et $\lfloor\log_2 n_d\rfloor\leqslant h_d\leqslant n_d-1$. On suppose par exemple que $n_g\geqslant n_d$.
    • Alors $h=1+\max(h_g,h_d)\leqslant 1+\max(n_g -1, n_d -1)=n_g\leqslant n_g+n_d - 1=n - 1$ car $n_d\geqslant1$.
    • Et $h\geqslant 1+\max(\lfloor\log_2 n_g\rfloor, \lfloor\log_2 n_d\rfloor)=\lfloor1+\log_2n_g\rfloor=\lfloor\log_2(2n_g)\rfloor\geqslant\lfloor\log_2(n_g+n_d)\rfloor=\lfloor\log_2n\rfloor$.
    • On a bien $\lfloor\log_2 n\rfloor\leqslant h\leqslant n-1$.

3) Voir ci-dessus.

4) Par récurrence d'ordre $2$ sur $h\in\mathbb N$.

  • Initialisation : $N_0=1=\mathcal F_2-1$ avec $\mathcal F_2=2$ et $N_1=2=\mathcal F_3-1$ avec $\mathcal F_3=3$ (la racine et une feuille).

  • Hérédité : Si $h\in\mathbb N^*$ tel que $N_{h-1}=\mathcal F_{h+1}-1$ et $N_{h-2}=\mathcal F_{h}-1$, on considère un arbre équilibré de hauteur $h$. Sa racine a un fils, disons le gauche, de hauteur $h-1$ et un fils droit de hauteur $h_d\in\{h-1, h-2\}$. Par hypothèse de récurrence, le fils gauche a au minimum $N_{h-1}=\mathcal F_{h+1}-1$ noeuds. Le nombre de noeuds du fils droit est au moins égal à $N_{h-2}=\mathcal F_{h}-1$. Et donc $N_h\geqslant 1+N_{h-1}+N_{h-2}=F_{h+1}+F_{h}-1\geqslant F_{h+2}-1$ ce qui établit la récurrence.

5) On a alors $N_h\sim C\cdot\varphi^h$ où $C$ est une constante et $\varphi$ le nombre d'or.

Donc, avec les règles usuelles (on n'est pas au voisinage de $1$), $h\log\varphi\sim \log N_h.$ Or $N_h\leqslant n$ d'où $h=O(\log n)$.

Par ailleurs, on a $n<2^{h+1}$ donc $\log n<h+1$ donc $h=\Omega(\log n)$.

On en déduit que $h=\Theta(\log n)$.

Exercice 6

In [10]:
type ('f, 'n) noeud = F of 'f | NI of 'n;;
Out[10]:
type ('f, 'n) noeud = F of 'f | NI of 'n

1) NoeudInterne code à l'arbre (triplet (fils gauche, étiquette, fils droit)) issu du noeud alors que NI code seulement à l'étiquette du noeud.

2)

  • Arbre 1
    • Parcours prefixe : [NI 1; F 2; NI 3; F 4; F 5]
    • Parcours infixe : [F 2; NI 1; F 4; NI 3; F 5]
    • Parcours postfixe : [F 2; F 4; F 5; NI 3; NI 1]
  • Arbre 2
    • Parcours prefixe : [NI 1; NI 3; F 5; F 4; F 2]
    • Parcours infixe : [F 5; NI 3; F 4; NI 1; F 2]
    • Parcours postfixe : [F 5; F 4; NI 3; F 2; NI 1]

3)

In [1]:
let rec parcours_prefixe arbre =
    match arbre with
    | Feuille f -> [F f]
    | NoeudInterne(fils_gauche, noeud, fils_droit) ->
    (NI noeud) :: (parcours_prefixe fils_gauche) @ (parcours_prefixe fils_droit)
;;
parcours_prefixe arbre;;
parcours_prefixe (symetrique arbre);;
File "[1]", line 3, characters 6-13:
Error: Unbound constructor Feuille
   2:     match arbre with
   3:     | Feuille f -> [F f]
   4:     | NoeudInterne(fils_gauche, noeud, fils_droit) ->

4) Dans le pire cas, l'arbre est complet et la complexité vérifie, pour une hauteur $h$, $T(0)=O(1)$ et $T(h)=2\cdot T(h-1)+O(2^h)$. Soit $\frac{T(h)}{2^h}=O(h)$ ou encore $T(h)=O\left(h2^h\right)$.

En fonction du nombre $n$ de noeuds, on obtient $T(n)=O\left(n\log n\right)$. Remarquons qu'un parcours visite toujours chaque noeud une et une seule fois, d'où un coût linéaire en le nombre de noeuds, auquel il faut ajouter un éventuel traitement à chaque noeud (ici, la concaténation des listes).

5)

In [12]:
let rec parcours_infixe arbre =
    match arbre with
    | Feuille f -> [F f]
    | NoeudInterne(fils_gauche, noeud, fils_droit) ->
    (parcours_infixe fils_gauche) @ ((NI noeud) :: parcours_infixe fils_droit)
;;
parcours_infixe arbre;;
parcours_infixe (symetrique arbre);;
Out[12]:
val parcours_infixe : ('a, 'b) arbre -> ('a, 'b) noeud list = <fun>
Out[12]:
- : (int, int) noeud list = [F 2; NI 1; F 4; NI 3; F 5]
Out[12]:
- : (int, int) noeud list = [F 5; NI 3; F 4; NI 1; F 2]
In [13]:
let rec parcours_suffixe arbre =
    match arbre with
    | Feuille f -> [F f]
    | NoeudInterne(fils_gauche, noeud, fils_droit) ->
    (parcours_suffixe fils_gauche) @ (parcours_suffixe fils_droit) @ [NI noeud]
;;
parcours_suffixe arbre;;
parcours_suffixe (symetrique arbre);;
Out[13]:
val parcours_suffixe : ('a, 'b) arbre -> ('a, 'b) noeud list = <fun>
Out[13]:
- : (int, int) noeud list = [F 2; F 4; F 5; NI 3; NI 1]
Out[13]:
- : (int, int) noeud list = [F 5; F 4; NI 3; F 2; NI 1]

6)

In [14]:
let parcours_prefixe arbre =
    let rec parcours_aux arbre accu =
        match arbre with
        | Feuille f -> (F f) :: accu
        | NoeudInterne(fils_gauche, noeud, fils_droit) -> 
            (NI noeud) :: parcours_aux fils_gauche (parcours_aux fils_droit accu)
    in
    parcours_aux arbre []
;;
parcours_prefixe arbre;;
Out[14]:
val parcours_prefixe : ('a, 'b) arbre -> ('a, 'b) noeud list = <fun>
Out[14]:
- : (int, int) noeud list = [NI 1; F 2; NI 3; F 4; F 5]

La complexité est bien linéaire (chaque noeud est parcouru une seule fois, les opérations pour chaque noeud sont à coût constant) mais la fonction auxiliaire n'est pas récursive terminale. En voici une version avec des fonctions auxiliaires récursives terminales.

In [15]:
let parcours_prefixe arbre =
    let rec parcours_envers arbre accu =
        match arbre with
        | Feuille f -> (F f) :: accu
        | NoeudInterne(fils_gauche, noeud, fils_droit) -> 
            parcours_envers fils_droit (parcours_envers fils_gauche ((NI noeud) :: accu))
    in
    List.rev (parcours_envers arbre [])
;;
parcours_prefixe arbre;;
Out[15]:
val parcours_prefixe : ('a, 'b) arbre -> ('a, 'b) noeud list = <fun>
Out[15]:
- : (int, int) noeud list = [NI 1; F 2; NI 3; F 4; F 5]

7)

    1
   / \
  2   4
 / \
4   5
 

8)

In [16]:
let reconstruire_prefixe liste =
    (* aux prend en argument une liste et renvoie un couple constitué de l'arbre construit 
    sur le début de la liste et de la liste des éléments restants. *)
    let rec aux liste =
        match liste with
        | (F f1) :: q -> (Feuille f1, q)
        | (NI n) :: q -> 
            let fils_gauche, liste1 = aux q in
            let fils_droit, liste2 = aux liste1 in
            (NoeudInterne (fils_gauche, n, fils_droit), liste2)
        | _ -> failwith "Cette liste ne correspond pas à un parcours prefixe."
    in
    let arbre, liste1 = aux liste in
    match liste1 with
    | [] -> arbre
    | _ -> failwith "Cette liste ne correspond pas à un parcours prefixe."
;;
    
reconstruire_prefixe [NI 1; F 2; NI 3; F 4; F 5];;
    
reconstruire_prefixe [NI 1; NI 3; F 5; F 4; F 2];;
    
reconstruire_prefixe [F 5; F 4; NI 3; F 2; NI 1];;
Out[16]:
val reconstruire_prefixe : ('a, 'b) noeud list -> ('a, 'b) arbre = <fun>
Out[16]:
- : (int, int) arbre =
NoeudInterne (Feuille 2, 1, NoeudInterne (Feuille 4, 3, Feuille 5))
Out[16]:
- : (int, int) arbre =
NoeudInterne (NoeudInterne (Feuille 5, 3, Feuille 4), 1, Feuille 2)
Exception:
Failure "Cette liste ne correspond pas \195\160 un parcours prefixe.".

9) Les arbres suivants ont même parcours infixe.

     1                  2
    / \                / \
   2   5      et      3   1
  / \                    / \
 3   4                  4   5