

## Les arbres binaires

>  Parmi la forêt d'arbres possibles, on s'intéressera essentiellement aux arbres dit binaires.
> Ceux ci sont principalement utilisés pour les bases de données (indexation), les moteurs de recherche, et les compilateurs.



### Définition:
Un arbre **binaire** est un arbre de degré 2 (dont lesnoeuds sont de degré 2 au plus).

Les enfants d'un noeud sont lus de **gauche à droite** et sont appelés **fils gauche** et **fils droit**.

![fig15.png](assets/fig15.png)

### Exercice 1:
Parmi les arbres du cours précédent, lesquels sont binaires ?

Réponse

### Remarque:
Les arbres binaires forment une structure de données qui peut se définir de façon récursive.

Un arbre binaire est:
- soit vide
- soit composé d'une racine portant une étiquette (clé) et d'une paire d'arbres binaires, appelés sous-arbre gauche et sous-arbre droit.

Les arbres binaires sont utilisés dans de très nombreuses activités informatiques, nous allons étudier et implanter cette structure de données.

## Comment représenter un arbre binaire... à la main...

L'idée est de représenter l'arbre avec un tableau.

[r, a, b] La racine r suivie de ses fils gauche et droit.

![fig16.png](assets/fig16.png)

![fig17.png](assets/fig17.png)

Puis on rajoute dans l'ordre les fils gauche et droit de a, puis ceux de b. 

[r, a, b, c, d, e, f]

### Remarque:
Chaque noeud se repère par son indice *n* dans la liste, son fils gauche se trouvant alors à l'indice *2n + 1* et son fils droit à l'indice *2n + 2*.

Exemple: **b** est d'indice 2, son fils gauche se trouve alors à l'indice 5 et son fils droit à l'indice 6.

Si un noeud n'a pas de fils, on le précise en mettant *None* à sa place. Notre arbre est alors représenté par le tableau:

[r, a, b, c, d, e, f, None, None, None, None, None, None, None, None]

**Quelle taille doit avoir le tableau ?**

![fig18.png](assets/fig18.png)

Cet arbre est complet (tous les noeuds internes ont deux fils).

Il possède 3 niveaux, sa hauteur ou profondeur est donc de 2. La taille du tableau sera de: $$2^0 + 2^1 + 2^2 + 2^3 = 2^4 -1 = 15$$

*Il faut compter le nombre de noeuds, y compris les noeuds "fantômes" des feuilles.*

### Exercice :
1. Quelle est la taille du tableau qui permet de représenter cet arbre ?

![fig19.png](assets/fig19.png)

2. Ecrire le tableau représentant cet arbre et le stocker dans une variable **t**

3. Quelle propriété ont les indices des fils gauches et droits ?

4. Voici un tableau représentant un arbre binaire:

['*','-',5,2,6,None,None,None,None,None,None,None,None,None,None]


Le dessiner. Que peut-il représenter ?


```python

```


```python

```


```python

```


```python

```

>  Voici un code python Python qui crée la liste représentant l'arbre de l'exercice 2



```python
def creation_arbre(r,profondeur):
    """r : la racine (str ou int). la profondeur de l’arbre (int)"""
    Arbre = [r]+[None for i in range(2**(profondeur+1)-2)]
    return Arbre

def insertion_noeud(arbre,n,fg,fd):
    """Insére les noeuds et leurs enfants dans l’arbre"""
    indice = arbre.index(n)
    arbre[2*indice+1] = fg
    arbre[2*indice+2] = fd

# création de l’arbre
arbre = creation_arbre("r",5)
# ajout des noeuds par niveau de gauche à droite
insertion_noeud(arbre,"r","a","b")
insertion_noeud(arbre,"a","c","d")
insertion_noeud(arbre,"b","e","f")
insertion_noeud(arbre,"c",None,"h")
insertion_noeud(arbre,"d","i","j")
insertion_noeud(arbre,"e","k",None)
insertion_noeud(arbre,"f",None,None)
insertion_noeud(arbre,"h",None,None)
insertion_noeud(arbre,"i",None,None)
insertion_noeud(arbre,"j","m",None)
insertion_noeud(arbre,"k",None,None)
insertion_noeud(arbre,"m",None,None)

#pour vérifier
print(len(arbre))
print(arbre)
```

    63
    ['r', 'a', 'b', 'c', 'd', 'e', 'f', None, 'h', 'i', 'j', 'k', None, None, None, None, None, None, None, None, None, 'm', None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None]


### Exercice 3:
1. Ecrire une fonction qui retourne le parent d'un noeud s'il est dans l'arbre et None sinon.
2. Ecrire une fonction qui retourne **True** si l'arbre est vide
3. Ecrire une fonction qui retourne les enfants d'un noeud.
4. Un fonction qui renvoie le fils gauche d'un noeud s'il existe et None sinon
5. Même question pour le fils droit
6. Ecrire une fonction qui renvoie **True** si le noeud est la racine de l'arbre
7. Ecrire une fonction qui renvoie **True** si le noeud est une feuille
8. Ecrire une fonction qui renvoie **true** si le noeud comporte un frère gauche ou droit.

**<u>Rappel : dans un tableau, si on note i l'indice d'un noeud, alors ses fils auront comme indice 2i +1 et 2i +2.</u>**

**<u>Inversement, pour trouver le parent d'un noeud, il faut vérifier : i - 1  //2</u>**


```python

```


```python

```


```python

```


```python

```


```python

```


```python

```


```python

```


```python

```

---------

## Une seconde façon de faire...

Comme vous l'avez sans doute constaté, il est assez fastidieux de représenter un arbre avec un unique tableau surtout pour un arbre très profond.

L'idée est de représenter l'arbre ave cun tableau contenant des tableaux

![fig20.png](assets/fig20.png)

Cet arbre se représente par le tableau:

['r', ['a', [ ], [ ]], ['b', [ ], [ ]]]

### Exercice :
Ecrire le tableau représentant l'arbre ci-dessous et le stocker dans une variable **t**

![fig21.png](assets/fig21.png)

**Le code Python ci-dessous, construit l'arbre de l'exercice 1 de manière récursive.**

* Les noeuds sont représentés par un dictionnaire.
* L'arbre se construit depuis la racine en construisant les sous-arbres des fils gauche et droit.


```python
def noeud(nom, fg =None, fd =None):
    return{'racine': nom, 'fg' : fg, 'fd': fd}

# création des noeuds
k = noeud('k')
f = noeud('f')
e = noeud('e', k,None)
b = noeud('b', e, f)
m = noeud('m')
j = noeud('j', m,None)
i = noeud('i')
d = noeud('d', i, j)
h = noeud('h')
c = noeud('c',None, h)
a = noeud('a', c, d)
racine = noeud('r', a, b)
# création de l’arbre
def construit(arbre):
    if arbre is None:
        return [ ]
    else:
        return [arbre['racine'],construit(arbre['fg']),construit(arbre['fd'])]

arbre1 = construit(racine)
print(arbre1)
```

    ['r', ['a', ['c', [], ['h', [], []]], ['d', ['i', [], []], ['j', ['m', [], []], []]]], ['b', ['e', ['k', [], []], []], ['f', [], []]]]


### Exercice : 
Ecrire toutes les fonctions de l'exercice 3 dans le cas de cette implémentation de l'arbre.


```python

```


```python

```


```python

```


```python

```


```python

```


```python

```


```python

```


```python

```

Nous allons maintenant nous intéresser à la hauteur de l'arbre, ainsi qu'aux différentes façons de le parcourir:
* Le parcours en largeur
* Le parcours en profondeur (parcours fixe, parcours infixe et parcours suffixe).

**Calcul de la hauteur de l'arbre:**
L'idée ets la suivante:
* Si l'arbre est vide, la hauteur vaut -1
* Sinon la hauteur vaut 1 auquel il faut ajouter le maximum entre les hauteurs des sous arbres gauche et droit.
* Ces sous-arbres sont eux même des arbres dont il faut calculer la hauteur.

Une méthode **récursive** semble donc tout à fait adaptée à la situation.

Voici l'algorithme:

![fig22.png](assets/fig22.png)

### Exercice :
1. Ecrire cette fonction pour l'arbre précédent et vérifier que sa profondeur est de 4.


```python

```

## Les parcours

**Le parcours en largeur:**
Le parcours en largeur d'un arbre consiste à partir de la racine. On visite ensuite son fils gauche puis sont fils droit, puis le fils gauche du fils gauche etc... Comme le montre le schéma ci-dessous:

![fig23.png](assets/fig23.png)


L'idée est la suivante:

On utilise une File.
* On met l'arbre dans la file.
* Puis tant que la file n'est pas vide:
    * On défile la file
    * On récupère sa racine
    * On enfile son fils gauche s'il existe
    * on enfile son fils droit s'il existe
    

Voici l'algorithme:

![fig25.png](assets/fig25.png)

### Exercice 7

1. Rappeler les fonction permettant de définir une structure de file en python.
2. Implémenter alors cette fonction et l'essayer sur l'arbre précédent.


```python

```


```python

```

**Les parcours en profondeur**
On se balade autour de l'arbre en suivant les pointillés

![fig24.png](assets/fig24.png)

Voici un algorithme permettant de réaliser ce parcours:
![fig26.png](assets/fig26.png)

### Exercice 
1. Proposer une fonction permettant de réaliser le parcours en profondeur d'un arbre


```python

```

Dans le schéma ci-dessus, on a rajouté des "noeuds fantômes" pour montrer que l'on peut considérer que chaque noeud est visité trois fois:
* une fois par la gauche
* une fois par en dessous
* une fois par la droite

### Definition:
Dans un parcours **prefixe**, on liste le noeud la première fois qu'on le rencontre.


### Exercice 
1. Ecrire les sommets dans l'ordre d'un parcours préfixe



### Definition:
Dans un parcours **infixe**, on liste le noeud la seconde fois qu'on le rencontre.

Ce qui correspond à:
* On liste chaque noeud ayant un fils gauche la seconde fois qu'on le voit
* on liste chaque noeud sans fils gauche la première fois qu'on le voit

### Exercice 
1. Ecrire les sommets dans l'ordre d'un parcours infixe



### Definition:
Dans un parcours **suffixe**, on note le noeud la dernière fois qu'on le rencontre.

Exercice 11
1. Ecrire les sommets dans l'ordre d'un parcours suffixe



### Exercice 
1. Voici trois algorithmes récursifs, dire pour chacun d'entre eux à quel parcours il correspond.
![fig27.png](assets/fig27.png)
![fig28.png](assets/fig28.png)
![fig29.png](assets/fig29.png)
2. Implémenter ces trois fonctions en python et confirmer les réponses des questions précédentes

-------

## Une troisième façon de faire.... en programmation objet

Nous allons créer une classe **Noeud** dont les attributs d'instances seront:
* le nom (ou valeur) de la racine
* son fils gauche (None par défaut) ou de type Noeud
* son fils droit (None par défaut) ou de type Noeud

### Exercice 
1. Implémentez cette classe Noeud
2. Stocker l'arbre comme étant l'objet de type "Noeud" correspondant à la racine.
3. Ecrire une méthode estFeuille qui renvoie True si le noeud est une feuille et False sinon
4. Définir une fonction hauteur prenant comme argument un arbre permettant de renvoyer la hauteur de cet arbre
5. Ecrire une méthode hauteur dans la classe Noeud permettant de faire la même chose
6. Proposer des fonctions pour le parcours infixe, prefixe et suffixe pour cette façon de coder un arbre.
7. Transformer alors vos fonctions en méthodes de la classe Noeud


```python

```

-------

### Sources

- [Cours de Stephan Van Zulen](https://www.nsi-ljm.fr/NSI-TLE/co/section_chapitre3.html)
- [Cours de Jeremy Camponovo](https://github.com/jcamponovo)
