Arbres binaires
Définitions
En mathématique: Un arbre est un cas spécial de graphe acyclique oriente, ou tous les nœuds sauf la racine ont un unique parent
En informatique: C’est une structure de données récursive utilisée pour représenter ce type de graphes.
Exemples et terminologie
Un arbre contenant des entiers
Un arbre est un ensemble de nœuds et d’arête reliant ces
nœudsChaque nœud a une étiquette : une valeur ou donnée associée au
nœud.
L’ordre ou la taille d’un arbre est le nombre de nœuds qui le
compose.
La racine d’un arbre est le nœud le plus haut dans l’hiérarchie de
l’arbre, c’est le nœud “de départ”.
Tous les nœuds sauf la racine ont un unique père, c’est le nœud
directement au-dessus dans l’hiérarchie.
Les nœuds ont 0, 1, ou plusieurs fils. Les nœuds sans fils sont
appelé feuilles de l’arbre.
Le degré d’un nœud est le nombre de fils qu’il a.
La profondeur d’un nœud est le nombre d’arête nécessaire pour aller
du nœud a la racine.
On s’intéresse dans ce cours aux arbres binaires, c-a-d, les arbres
ayant des nœuds de degré maximum 2.\
Classe de complexité
Structure récursive
On peut définir les arbres binaires de manière récursive. Un arbre est :
Soit vide, représenté par une liste vide
[]
.Soit compose d’un nœud racine ayant une valeur $V_{r}$, d’un sous-arbre gauche $F_{g}$, et d’un sous-arbre droit $F_{d}$. On représente l’arbre par la liste
[V_r, F_g, F_d]
.
Exemples
[2, [], []]
[4, [2, [], []], [8, [1, [], []], []] ]
[7, [3, [1, [], []], [2, [], []]], [5, [4, [], []], []] ]
Implémentation
def crée_arbre():
return []
def arbre(R, Fg, Fd):
return [R, Fg, Fd]
def est_vide(A):
return A == []
def fils_g(A):
if est_vide(A): return None
return A[1]
def fils_d(A):
if est_vide(A): return None
return A[2]
def racine(A):
if est_vide(A): return None
return A[0]
def est_arbre(A):
return est_vide(A) or (isinstance(A, list) and len(A) ==3 and est_arbre(fils_g(A)) and est_arbre(fils_d(A))
def est_feuille(A):
return not est_vide(A) and est_vide(fils_g(A)) and est_vide(fils_d(A))
Parcours
On peut parcourir l’arbre en privilégiant la largeur, ou la profondeur.
Parcours en profondeur (Depth-First Search):
Préfixe :
On parcourt l’arbre récursivement suivant l’ordre : $V_{r},F_{g},F_{d}$
Infixe :
On parcourt l’arbre récursivement suivant l’ordre : $F_{g},V_{r},F_{d}$
Postfix :
On parcourt l’arbre récursivement suivant l’ordre : $F_{g},F_{d},V_{r}$
Parcours en largeur (Breadth-First Search)
On liste tous les fils du même niveau de profondeur avant de passer au niveau de profondeur suivant.
Implémentation
def parcours_préfixe(A):
if est_vide(A) :
return []
return [racine(A)] + parcours_préfixe(fils_g(A)) + \
parcours_préfixe(fils_d(A))
def parcours_infixe(A):
if est_vide(A) :
return []
return parcours_infixe(fils_g(A)) + [racine(A)] + \
parcours_infixe(fils_d(A))
def parcours_postfix(A):
if est_vide(A) :
return []
return parcours_postfix(fils_g(A)) + parcours_postfix(fils_d(A)) + \
[racine(A)]
def BFS(A):
F = [A]
while F!=[]:
x = F.pop(0)
# Traitement sur x
print(racine(x))
# Ajout des fils dans la file
if not est_vide(fils_g(A)):
F.append(fils_g(x))
if not est_vide(fils_d(A)):
F.append(fils_d(x))
Arbre Binaire de Recherche (Binary Search Tree)
Un ABR A
est un arbre binaire qui vérifie les conditions suivantes :
Les étiquettes du sous-arbre gauche sont inférieures a celle de la racine
Les étiquettes du sous-arbre droit sont supérieures a celle de la racine
Les sous-arbres gauche et droit sont des ABR (récursivité)
Un ABR
On peut obtenir les étiquettes d’un ABR en ordre croissant en faisant un parcours infixe.
Implémentation
def min_abr(ABR):
current = ABR
while not est_vide(fils_g(current)):
current = fils_g(current)
return racine(current)
def max_abr(ABR):
current = ABR
while not est_vide(fils_d(current)):
current = fils_d(current)
return racine(current)
def insert(ABR, e):
if est_vide(ABR):
ABR = [e, [], []]
elif e <= racine(ABR) :
insert(fils_g(ABR), e)
else :
insert(fils_d(ABR), e)
def search(ABR, e):
if est_vide(ABR) :
return False
if racine(ABR) == e:
return True
if e < racine(ABR) :
return search(fils_g(ABR), e)
if e > racine(ABR) :
return search(fils_d(ABR), e)
def prédécesseur(ABR, e):
if est_vide(fils_g(ABR)) :
return None
return max_abr(fils_g(ABR))
def successeur(ABR, e):
if est_vide(fils_d(ABR)) :
return None
return min_abr(fils_d(ABR))
def remove(ABR, e):
# ...
Tas
Un Tas T
est un arbre binaire qui vérifie les conditions suivantes :
Il est parfait, c’est a dire tous les niveaux sauf le dernier doivent être totalement remplis et le dernier niveau est rempli de gauche à droite.
l’étiquette de chaque nœud est supérieure ou égale (resp. inférieure ou égale) aux étiquettes de ses fils, on parle de tas max (resp. tas min)
Un tas
Implémentation
Le tas étant un arbre binaire parfait, on peut l’implémenter en
utilisant une seule liste, ou on met la racine a l’indice 0, et pour
chaque nœud i
on met les fils gauche et droit aux position 2*i+1
et
2*i+2
.
Le tas précédent est donc implémente comme suit :
T = [53, 41, 30, 36, 28, 21, 6, 31, 16, 20]
def crée_tas():
return []
def insert(T, e):
# ...
def enlève_racine(T):
# ...
Tri par tas (Heap sort)
On peut utiliser les tas pour faire le tri: Pour une liste L
, on
insère successivement les elements de L
dans un tas min, puis on
enlève la racine successivement jusqu’a vider le tas, la racine étant le
min a chaque itération, l’ordre des racine enlève sera donc croissant.
def tri_par_tas(L):
# ...