---
title: "Arbres"
date: "2024-03-05"
---

# Les arbres

{\Huge Les arbres}

# Les arbres: définition

"Un arbre est un graphe acyclique orienté possédant une unique racine, et tel que tous les nœuds sauf la racine ont un unique parent."

. . .

**Santé!**

## Plus sérieusement

* Ensemble de **nœuds** et d'**arêtes** (graphe),
* Les arêtes relient les nœuds entre eux, mais pas n'importe comment: chaque
  nœud a au plus un **parent**,
* Le seul nœud sans parent est la **racine**,
* Chaque nœud a un nombre fini d'**enfants**,
* La hiérarchie des nœuds rend les arêtes **orientées** (parent -> enfants), et empêche les
  **cycles** (acyclique, orienté).
* La **feuille** ou **nœud terminal** est un nœud sans enfants,
* Le **niveau** est 1 à la racine et **niveau+1** pour les enfants,
* Le **degré** d'un nœud est le nombre de enfants du nœud.

. . .

* Chaque nœud est un arbre en lui même.
* La **récursivité** sera très utile!


# Arbre ou pas arbre?

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    1-->2;
    1-->3;
    3-->2;
    3-->4;
    3-->5;
```
::::

. . .

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    1-->2;
    1-->3;
    3-->4;
    3-->5;
    3-->6;
```
::::

:::

# Arbre ou pas arbre?

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    1-->2;
    1-->3;
    3-->4;
    3-->5;
    3-->6;
    6-->7;
    7-->3;
```
::::

. . .

:::: column
```{.mermaid format=pdf width=300 loc=figs/}
graph TD;
    1;
```
::::

:::

# Arbre ou pas arbre?

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    1---2;
    1---3;
    3---4;
    3---5;
```
::::

. . .

:::: column
```{.mermaid format=pdf width=300 loc=figs/}
graph BT;
    1-->2;
    1-->3;
    3-->4;
    3-->5;
    3-->6;
```
::::

:::

# Degré et niveau

* Illustration du degré (nombre d'enfants) et du niveau (profondeur)

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    1[degré 2]-->2[degré 0];
    1-->3[degré 3];
    3-->4[degré 0];
    3-->5[degré 0];
    3-->6[degré 0];
```
::::

. . .

:::: column
```{.mermaid format=pdf width=300 loc=figs/}
graph TD;
    1[niveau 1]-->2[niveau 2];
    1-->3[niveau 2];
    3-->4[niveau 3];
    3-->5[niveau 3];
    3-->6[niveau 3];
```
::::

:::

* Les nœuds de degré 0, sont des feuilles.

# Application: recherche rapide

## Pouvez vous construire un arbre pour résoudre le nombre secret?

 . . .

* Le nombre secret ou la recherche dichotomique (nombre entre 0 et 10).

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph LR;
    5-->|<|2;
    5-->|>|7;
    7-->|>|8;
    7-->|<|6;
    8-->|>|9;
    9-->|>|10;
    2-->|<|1;
    2-->|>|3;
    3-->|>|4;
    1-->|<|0;
```
::::

:::: column

**Question:** Quelle est la complexité pour trouver un nombre?

::::

:::

# Autres représentation

* Botanique
* **Exercice:** Ajouter les degrés/niveaux et feuilles

```{.mermaid width=250 format=pdf loc=figs/}
graph TD;
    A-->B;
    A-->C;
    B-->D;
    B-->E;
    B-->F;
    F-->I;
    F-->J;
    C-->G;
    C-->H;
    H-->K;
```

# Autres représentation

* Ensembliste

::: columns

:::: column
```{.mermaid width=300 format=pdf loc=figs/}
graph TD;
    A-->B;
    A-->C;
    B-->D;
    B-->E;
    B-->F;
    F-->I;
    F-->J;
    C-->G;
    C-->H;
    H-->K;
```
::::

. . .

:::: column
![](figs/ensemble.svg)
::::

:::

# Autres représentation

* Liste

::: columns

:::: column
```{.mermaid width=400 format=pdf loc=figs/}
graph TD;
    A-->B;
    A-->C;
    B-->D;
    B-->E;
    B-->F;
    F-->I;
    F-->J;
    C-->G;
    C-->H;
    H-->K;
```
::::

. . .

:::: column
```
(A 
    (B 
        (D) 
        (E) 
        (F 
            (I) 
            (J)
        )
    ) 
    (C
        (G) 
        (H 
            (K)
        )
    )
)
```
::::

:::

# Autres représentation

* Par niveau

::: columns

:::: column
```{.mermaid width=400 format=pdf loc=figs/}
graph TD;
    A-->B;
    A-->C;
    B-->D;
    B-->E;
    B-->F;
    F-->I;
    F-->J;
    C-->G;
    C-->H;
    H-->K;
```
::::

. . .

:::: column
```
1       2       3       4
-------------------------
A       
        B       
                D 
                E 
                F           
                        I
                        J
        C       
                G
                H
                        K
```
::::

:::

# L'arbre binaire

* Structure de données abstraite,
* Chaque nœud a au plus deux enfants: gauche et droite,
* Chaque enfants est un arbre.

## Comment représenteriez vous une telle structure?

. . .

```C
<R, G, D>
    R: racine
    G: sous-arbre gauche
    D: sous-arbre droite
```

## Comment cela s'écrirait en C?

. . .

```C
typedef struct _node {
    contenu info;
    struct _node *left, *right;
} node;
typedef node *tree;
```

# L'arbre binaire

## Que se passerait-il avec 

```C
typedef struct _node {
    int info;
    struct _node left, right;
} node;
```

* On ne sait pas quelle est la taille de node, on ne peut pas l'allouer!

## Interface minimale

* Qu'y mettriez vous?

. . .

```C 
NULL              -> arbre (vide)
<n, arbre, arbre> -> arbre
visiter(arbre)    -> nœud (la racine de l'arbre)
gauche(arbre)     -> arbre (sous-arbre de gauche)
droite(arbre)     -> arbre (sous-arbre de droite)
```

* Les autres opérations (insertion, parcours, etc) dépendent de ce qu'on stocke
  dans l'arbre.

# Exemple d'arbre binaire

* Représentez `(c - a * b) * (d + e / f)` à l'aide d'un arbre binaire (matrix)

. . .

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    A[*]-->B[-];
    B-->C[c];
    B-->D[*];
    D-->E[a];
    D-->F[b];
    A-->G[+];
    G-->H[d];
    G-->I["/"];
    I-->J[e];
    I-->K[f];
```
::::


:::: column

## Remarques

* L'arbre est **hétérogène**: le genre d'info est pas le même sur chaque nœud
  (opérateur, opérande).
    * Les feuilles contiennent les opérandes.
    * Les nœuds internes contiennent les opérateurs.

::::

:::

# Parcours d'arbres binaires

* Appliquer une opération à tous les nœuds de l'arbre,
* Nécessité de **parcourir** l'arbre,
* Utiliser uniquement l'interface: visiter, gauche,
  droite.

## Une idée de comment parcourir cet arbre?

* 3 parcours (R: Racine, G: sous-arbre gauche, D: sous-arbre droit):


::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    A[*]-->B[-];
    B-->C[c];
    B-->D[*];
    D-->E[a];
    D-->F[b];
    A-->G[+];
    G-->H[d];
    G-->I["/"];
    I-->J[e];
    I-->K[f];
```
::::

:::: column

1. Parcours **préfixe** (R, G, D),
2. Parcours **infixe** (G, R, D),
3. Parcours **postfixe** (G, D, R).

::::

:::

# Le parcours infixe (G, R, D)

* Gauche, Racine, Droite:
    1. On descend dans l'arbre de gauche tant qu'il est pas vide,
    2. On visite la racine du sous arbre,
    3. On descend dans le sous-arbre de droite (s'il est pas vide),
    4. On recommence.

. . .

## Incompréhensible?

* La récursivité c'est la vie.
    
```
parcours_infixe(arbre a)
    si est_pas_vide(gauche(a))
       parcours_infixe(gauche(a))
    visiter(A)
    si est_pas_vide(droite(A))
       parcours_infixe(droite(A))
```

# Graphiquement (dessinons)

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    A[*]-->B[-];
    B-->C[c];
    B-->D[*];
    D-->E[a];
    D-->F[b];
    A-->G[+];
    G-->H[d];
    G-->I["/"];
    I-->J[e];
    I-->K[f];
```
::::

:::: column

```
parcours_infixe(arbre a)
    si est_pas_vide(gauche(a))
       parcours_infixe(gauche(a))
    visiter(A)
    si est_pas_vide(droite(A))
       parcours_infixe(droite(A))
```

::::

:::


# Graphiquement (`mermaid` c'est super)

::: columns

:::: column
```{.mermaid format=pdf width=400 loc=figs/}
graph TD;
    A[*]-->B[-];
    A[*]-.->|1|B[-];
    B-->C[c];
    B-.->|2|C[c];
    C-.->|3|B;
    B-->D[*];
    B-.->|4|D;
    D-->E[a];
    D-.->|5|E;
    E-.->|6|D;
    D-->F[b];
    D-.->|7|F;
    F-.->|8|A;
    A-->G[+];
    A-.->|9|G;
    G-->H[d];
    G-.->|10|H;
    H-.->|11|G;
    G-->I["/"];
    G-.->|12|I;
    I-->J[e];
    I-.->|13|J;
    J-.->|14|I;
    I-->K[f];
    I-.->|15|K;
```
::::

:::: column

```
parcours_infixe(arbre a)
    si est_pas_vide(gauche(a))
       parcours_infixe(gauche(a))
    visiter(A)
    si est_pas_vide(droite(A))
       parcours_infixe(droite(A))
```

## Remarque 

Le nœud est visité à la **remontée**.

## Résultat

```
c - a * b * d + e / f
```

::::

:::

# Et en C?

## Live code

\footnotesize

. . .

```C
typedef int data;
typedef struct _node {
    data info;
    struct _node* left;
    struct _node* right;
} node;
typedef node* tree_t;
void tree_print(tree_t tree, int n) {
    if (NULL != tree) {
        tree_print(tree->left, n+1);
        for (int i = 0; i < n; i++) {
            printf(" ");
        }
        printf("%d\n", tree->info);
        tree_print(tree->right, n+1);
    }
}
```

# Question

## Avez-vous compris le fonctionnement?

. . .

## Vous en êtes sûr·e·s?

. . .

## OK, alors deux exercices:

1. Écrire le pseudo-code pour le parcours R, G, D (matrix).
2. Écrire le pseudo-code pour la parcours G, D, R (matrix),

## Rappel

```
parcours_infixe(arbre a)
    si est_pas_vide(gauche(a))
       parcours_infixe(gauche(a))
    visiter(a)
    si est_pas_vide(droite(a))
       parcours_infixe(droite(a))
```

# Correction

\footnotesize

* Les deux parcours sont des modifications **triviales**[^2] de l'algorithme
  infixe.

## Le parcours postfixe

```python
parcours_postfixe(arbre a)
    si est_pas_vide(gauche(a))
       parcours_postfixe(gauche(a))
    si est_pas_vide(droite(a))
       parcours_postfixe(droite(a))
    visiter(a)
```

## Le parcours préfixe

```python
parcours_préfixe(arbre a)
    visiter(a)
    si est_pas_vide(gauche(a))
        parcours_préfixe(gauche(a))
    si est_pas_vide(droite(a))
        parcours_préfixe(droite(a))
```

. . .

**Attention:** L'implémentation de ces fonctions en C sont **à faire** en
exercice (inspirez vous de ce qu'on a fait avant)!

# Exercice: parcours

## Comment imprimer l'arbre ci-dessous?

```
                        f
                /
                        e
        +
                d
*
                c
        -
                        b
                *
                        a
```

. . .

## Bravo vous avez trouvé! 

* Il s'agissait du parcours D, R, G.

# Implémentation

## Vous avez 5 min pour implémenter cette fonction et la poster sur matrix!

. . .

```C 
void pretty_print(tree_t tree, int n) {
    if (NULL != tree) {
        pretty_print(tree->right, n+1);
        for (int i = 0; i < n; ++i) {
            printf(" ");
        }
        printf("%d\n", tree->info);
        pretty_print(tree->left, n+1);
    }
}
```

# Exercice supplémentaire (sans corrigé)

Écrire le code de la fonction 

```C
int depth(tree_t t);
```

qui retourne la profondeur maximale d'un arbre.

Indice: la profondeur à chaque niveau peut-être calculée à partir du niveau des
sous-arbres de gauche et de droite.

# La recherche dans un arbre binaire

* Les arbres binaires peuvent retrouver une information très rapidement.
* À quelle complexité? À quelle condition?

. . .

## Condition

* Le contenu de l'arbre est **ordonné** (il y a une relation d'ordre (`<`, `>`
  entre les éléments).

## Complexité

* La profondeur de l'arbre (ou le $\mathcal{O}(\log_2(N))$)

. . .

## Exemple: les arbres lexicographiques

* Chaque nœud contient une information de type ordonné, la **clé**,
* Par construction, pour chaque nœud $N$:
    * Toutes clé du sous-arbre à gauche de $N$ sont inférieurs à la clé de $N$.
    * Toutes clé du sous-arbre à droite de $N$ sont inférieurs à la clé de $N$.

# Algorithme de recherche

* Retourner le nœud si la clé est trouvée dans l'arbre.

```python
arbre recherche(clé, arbre)
    tante_que est_non_vide(arbre)
        si clé < clé(arbre)
            arbre = gauche(arbre)
        sinon si clé > clé(arbre)
            arbre = droite(arbre)
        sinon
            retourne arbre
    retourne NULL
```

# Algorithme de recherche, implémentation (live)

\footnotesize

. . .

```C 
typedef int key_t;
typedef struct _node {
    key_t key;
    struct _node* left;
    struct _node* right;
} node;
typedef node* tree_t;
tree_t search(key_t key, tree_t tree) {
    tree_t current = tree;
    while (NULL != current) {
        if (current->key > X) {
            current = current->gauche;
        } else if (current->key < X){
            current = current->droite;
        } else {
            return current;
        }
    }
    return NULL;
}
```

# Exercice (5-10min)

Écrire le code de la fonction

```C 
int tree_size(tree_t tree);
```

qui retourne le nombre total de nœuds d'un arbre et poster le résultat sur
matrix.

Indication: la taille, est 1 + le nombre de nœuds du sous-arbre de gauche
additionné au nombre de nœuds dans le sous-arbre de droite.

. . .

```C
int arbre_size(tree_t tree) {
    if (NULL == tree) {
        return 0;
    } else {   
        return 1 + tree_size(tree->left) 
            + tree_size(tree->right);
    }
}
```

# L'insertion dans un arbre binaire

* C'est bien joli de pouvoir faire des parcours, recherches, mais si on peut
  pas construire l'arbre....

## Pour un arbre lexicographique

* Rechercher la position dans l'arbre où insérer.
* Créer un nœud avec la clé et le rattacher à l'arbre.

# Exemple d'insertions

* Clés uniques pour simplifier.
* Insertion de 5, 15, 10, 25, 2, -5, 12, 14, 11.
* Rappel:
    * Plus petit que la clé courante => gauche,
    * Plus grand que la clé courante => droite.
* Faisons le dessins ensemble

```









```

## Exercice (3min, puis matrix)

* Dessiner l'arbre en insérant 20, 30, 60, 40, 10, 15, 25, -5 


# Pseudo-code d'insertion (1/2)

* Deux parties:
    * Recherche le parent où se passe l'insertion.
    * Ajout de l'enfant dans l'arbre.

## Recherche du parent

```
arbre position(arbre, clé)
    si est_non_vide(arbre)
        si clé < clé(arbre)
            suivant = gauche(arbre)
        sinon
            suivant = droite(arbre)
        tant que clé(arbre) != clé && est_non_vide(suivant)
            arbre = suivant
            si clé < clé(arbre)
                suivant = gauche(arbre)
            sinon
                suivant = droite(arbre)
            
    retourne arbre
```

# Pseudo-code d'insertion (2/2)

* Deux parties:
    * Recherche de la position.
    * Ajout dans l'arbre.

## Ajout de l'enfant

```
ajout(arbre, clé)
    si est_vide(arbre)
        arbre = nœud(clé)
    sinon
        si clé < clé(arbre)
            gauche(arbre) = nœud(clé)
        sinon si clé > clé(arbre)
            droite(arbre) = nœud(clé)
        sinon
            retourne
```

# Code d'insertion en C

## Recherche du parent (ensemble)

. . .

```C
tree_t position(tree_t tree, key_t key) {
    tree_t current = tree;
    if (NULL != current) {
        tree_t subtree = key > current->key ? current->right :
        current->left;
        while (key != current->key && NULL != subtree) {
            current = subtree;
            subtree = key > current->key ? current->right :
            current->left;
        }
    }
    return current;
}
```

[^2]: Copyright cours de mathématiques pendant trop d'années.