Un arbre est un ensemble organisé de noeuds.
Les arbres sont des structures éminemment récursives.
Un arbre est constitué :
Une arbre binaire est un arbre dont chaque noeud a au plus deux fils : le fils gauche et le fils droit
Nous allons voir maintenant des opérations sur les arbres qui vont nous permettre de prendre diverses mesures afin de déterminer leur complexité.
La taille d'un arbre B correspond au nombre de noeuds. Elle est définie par:
T(B) = 0, si B est un arbre vide
T(B) = 1 + T(g(B)) + T(d(B)), sinon
La hauteur ou profondeur est définie par :
H(x) = 0, si x est la racine de B
H(x) = 1 + H(y), si y est le père de x.
La hauteur ou profondeur d'un arbre B est définie par :
H(B) = max(H(x)), avec x représentant les noeuds de B.
La longueur de cheminement d'un arbre B est définie par :
LC(B) = ΣH(x) avec x représentant les noeuds de B
La profondeur moyenne d'un arbre B est définie par :
PM(B) = LC(B)/T(B)
Soit la longueur de cheminement divisée par la taille.
Note : Des mesures comme la longueur de cheminement et la profondeur moyenne seront très utile pour déterminer la complexité des algorithmes appliqués aux arbres binaires.
Pour clarifier tout cela, nous allons prendre l'arbre de la figure ci-dessous et préciser ses caractéristiques et mesures.
Pour cela nous appellerons cet arbre B et conserverons la numérotation des nœuds de l'arbre, ce qui donne :
- 1 est la racine de B, 2 est son fils gauche et 3 son fils droit
- les frères sont : (2,3), (4,5), (6,7), (9,10), (12,13)
- 1, 2, 3, 5 et 7 sont des points doubles de B
- 4 est un point simple à gauche de B
- 6, 8 et 9 est un point simple à droite de B
- 10, 11, 12, 13 et 14 et 15 sont des feuilles de B
- (1, 2, 4, 8) et (1, 3, 7, 13) sont les bords gauches et droits de B
- Les hauteurs des noeuds de l'arbre B sont :
En 0 le noeud 1, en 1 les noeuds 2 et 3, en 2 les noeuds 4, 5, 6 et 7, en 3 les noeuds 8, 9, 10, 11, 12 et 13,
en 4 les noeuds 14 et 15
- la hauteur de l'arbre B est H(B)=4
- la taille de l'arbre B est T(B)=15
- Les longueurs de cheminement et profondeurs moyennes de l'arbre B sont :
LC(B)=36, PM(B)=36/15 = 2.4
Leur spécificité permet de modifier les algorithmes sur les arbres, voire d’utiliser des algorithmes propres.
Les arbres complets voient tous leurs niveaux remplis.
Les arbres parfaits voient tous leurs niveaux remplis excepté le dernier qui est rempli de gauche à droite.
Les arbres dégénérés ne sont constitués que de points simples à gauche ou à droite.
Un arbre binaire complet :
Un arbre binaire de hauteur h contient donc au plus 2^h - 1 nœuds
Toute collection de n éléments dont les clés appartiennent à un ensemble ordonné peut être stockée et classée à l'intérieur d'un arbre binaire de recherche de n noeuds.
Dans ce cas, les noeuds contiennent les éléments et les liens père-fils permettent de gérer l'ordre entre ces éléments.
Un Arbre Binaire de Recherche (ABR) est un arbre binaire étiqueté tel qu'en tout noeud v de l'arbre:
- les éléments du sous-arbre gauche de l'arbre de racine v sont inférieurs ou égaux à v,
- les éléments du sous-arbre droit de l'arbre de racine v sont strictement supérieurs à v.
Il peut y avoir plusieurs ABRs représentant un même ensemble de données, comme le montre l'exemple suivant (cf. figure 1), pour l'ensemble d'entiers E = {6,8,10,11,14,16,18,30,33}.
- si B est un arbre vide, la recherche est négative
- si x est égal à l'élément de la racine de B, la recherche est positive
- si x est inférieur à l'élément de la racine de B, la recherche se poursuit sur le sous-arbre gauche de B
- si x est supérieur à l'élément de la racine de B, la recherche se poursuit sur le sous-arbre droit de B
- Déterminer la place d'ajout en fonction de l'ordre
- Réaliser l'ajout de l'élément
- Déterminer la place d'ajout en fonction de l'ordre
- Réaliser l'ajout de l'élément
On propose de modéliser en pseudo-code un arbre , comme une liste de nœuds, de la manière suivante :
Arbre = [
( etiquette 1, [ indice fils 1, indice fils 2, ..., indice fils n0 ] ),
( etiquette 2, [ indice fils 1, indice fils 2, ..., indice fils n1 ] ),
...
( etiquette n, [ indice fils 1, indice fils 2, ..., indice fils np ] )
]
Les nœuds d'arbres binaires n'ont que deux enfants. On pourra convenir d'un indice (par exemple -1) pour souligner l'absence de fils à gauche ou à droite.
Autrement dit :
- La valeur du noeud (étiquette ou clé)
- La liste des indices des nœuds fils
Par exemple, l'arbre suivant:
arbre_exemple = [(10, [1,2,3]),(20, [4,5,6]),(30, []),(40, []),(1, []),(2, []),(3,[7]),(4,[])]
On compte les indices à partir de 0. Puis on avance de 1 en 1 en progressant de haut en bas et à un même étage de gauche à droite.
Ainsi 10 est à l'indice 0, 20 à l'indice 1, 30 à l'indice 2 etc...
Voici un exemple d'implémentation de la fonction de recherche en pseudo-code. Nous allons le faire selon deux modes:
Ces fonctions partent du principe que vous avez codé quatre fonctions:
gauche(Arbre, Noeud) -> Renvoie le fils gauche dans l'arbre nommé Arbre du nœud donné en paramètre.
droite(Arbre, Noeud) -> Renvoie le fils droit dans l'arbre Arbre du nœud donné en paramètre.
get_racine(Arbre) -> Renvoie la racine de l'arbre Arbre donné en paramètre.
cle(Noeud)-> Renvoie l'étiquette du nœud donné en paramètre.
Un noeud est représenté par un couple comme décrit précédemment.
On utilise la représentation statique des arbres (à l'aide des tableaux), à ne pas confondre avec la représentation dynamique des arbres (à l'aide des classes).
Version récursive
fonction Rechercher(Arbre, noeud, x):
si noeud == None ou x == cle(noeud):
retourner noeud
sinon si x < cle(noeud):
retourner Rechercher(Arbre, gauche(Arbre, noeud), x)
sinon
retourner Rechercher(Arbre, droite(Arbre, noeud), x)
finsi
fin
On utilise la représentation statique des arbres (à l'aide des tableaux), à ne pas confondre avec la représentation dynamique des arbres (à l'aide des classes).
Version itérative
fonction RechercherIteratif(Arbre, x):
noeud = get_racine(Arbre)
tantque noeud != None et x != cle(noeud):
si x < cle(noeud)
noeud = gauche(Arbre, noeud)
sinon
noeud = droite(Arbre, noeud)
finsi
fin tantque
retourner noeud
fin
La procédure Insérer prend deux paramètres : Arbre (l'arbre dans lequel on veut faire l'insertion) et e (l'élément à insérer).
La procédure décrite dans la page suivante est un algorithme qui illustre le fonctionnement de l'insertion dans un arbre binaire de recherche. Pour cela elle fait appel à deux valeurs.
Version itérative
procedure Inserer(Arbre, e):
noeud_parent = None # sky is the only limit
noeud = get_racine(Arbre)
tantque noeud != None:
noeud_parent = noeud # Le parent descend d'un "étage"
si e < cle(noeud):
noeud = gauche(noeud) # L'enfant aussi
sinon
noeud = droite(noeud) # L'enfant aussi
finsi
fintantque
# Trouver le parent de e
# C'est l'élément noeud_parent
# Il faut lui dire qu'il a un nouvel enfant...
# La question est... à gauche ou à droite ??
# Tout dépend de la relation d'ordre de e vis-à-vis du noeud_parent !
indice_parent = Arbre.index(noeud_parent)
Arbre.ajouter( (e, []) )
tok = Arbre[indice_parent][0]
si e < tok:
Arbre[indice_parent] = (tok, [longueur(Arbre) - 1])
sinon:
Arbre[indice_parent] = (tok, [-1, longueur(Arbre) - 1])
finsi
finfonction