Arbres binaires de recherche

Lycée Faidherbe, Lille
MP1
Cours d’informatique
2013–2014
Arbres binaires
de recherche
I
Définitions
Parcours ordonné 2
II
...............................
Caractérisation 2
Opérations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Recherche 4 Insertion aux feuilles 4
: tri 8
Suppression 8
III
2
Implémentation 3
Exercices
Insertion à la racine 5
................................
3
Application
11
Parcours de recherche 11
Reconstruction 11
Fusion 11
Successeurs et prédécesseurs 11
Nombre de constructions 11
Vérification 12
Complexité moyenne d’une recherche 12
Rang et rang local 13
A.B.R.
page 1
I Définitions
I.1 Parcours ordonné
Un arbre binaire de recherche est un arbre binaire tel que tout nœud contient un
champ, noté cle ou etiquette, dont les valeurs sont entières (ou décrivent un
ensemble ordonné) et tel que le parcours infixe des clés de l’arbre donne une suite
croissante.
Exemple
22
13
28
18
11
31
16
I.2 Caractérisation
Un arbre binaire est de recherche si et seulement si pour tout
nœud x, de clé n, les clés des éléments de la branche gauche de
x sont inférieures ou égales à n et les clés des éléments de la
branche droite de x sont supérieures ou égales à n.
Sens direct
On suppose que l’arbre est un arbre binaire de recherche.
Pour tout sous–arbre les clés du fils gauche sont lues par le parcours infixe avant la
clé de la racine donc, comme le parcours est croissant, les clés du fils gauche sont
inférieures à celle de la racine.
De même les clés du fils droit sont supérieures à celle de la racine.
page 2
A.B.R.
Sens inverse
On vérifie la réciproque par récurrence sur la hauteur.
• C’est évident pour un sous–arbre de hauteur 0.
• Si c’est vrai pour tous les arbres de hauteur p au plus. Les fils droit et gauche
d’un arbre de hauteur p + 1 ont une hauteur inférieure ou égale à p. Le parcours
infixe de T se fait en parcourant le fils gauche, qui donne des clés croissantes
par hypothèse de récurrence, puis en lisant la clé de la racine, supérieure aux
précédentes, puis en parcourant le fils droit, toujours en croissant.
Le parcours infixe est croissant.
I.3 Implémentation
La déclaration du type et les fonctions de construction et déconstruction sont les
mêmes que dans le cas d’un arbre binaire classique.
Si on veut les employer il faut vérifier soi–même que les clés sont dans le bon ordre.
Dans toute la suite on va supposer que les clés sont distinctes.
type ’n abr =
Vide
|Noeud of (’n abr*’n *’n abr);;
let filsG = function
|Vide -> raise(Failure "Arbre vide")
|Noeud (g,_,_) -> g;;
let filsD = function
|Vide -> raise(Failure "Arbre vide")
|Noeud (_,_,d) -> d;;
let racine = function
|Vide -> raise(Failure "Arbre vide")
|Noeud (_,n,_) -> n;;
II Opérations
A.B.R.
page 3
II.1 Recherche
La recherche est simple : on “descend” dans l’arbre en choisissant le fils gauche ou
le fils droit selon que la valeur à chercher est inférieure ou supérieure à la clé du
nœud.
C’est le principe de la recherche par dichotomie
On parcourt ainsi (partiellement) une branche de l’arbre.
Programme
let rec chercher x = function
|Vide -> raise(Failure "Arbre vide")
|Noeud(g,n,d) as arbre -> if (n = x)
then arbre
else if (x < n)
then chercher x g
else chercher x d;;
Complexité
La complexité maximale est la hauteur de l’arbre.
Dans le cas des arbres équilibrés on sait que la longueur des branches est de l’ordre
de ln(n) où n est la taille de l’arbre.
Le nombre
de comparaisons pour trouver le sous–arbre recherché est alors un
O ln(n) .
II.2 Insertion aux feuilles
On peut ajouter un nouvel enregistrement à une feuille de l’arbre : on recherche
l’élément et une recherche infructueuse arrive à un sous–arbre Vide. On peut alors
y placer l’élément.
Si la recherche donne un résultat on ne change rien car on s’interdit les clés multiples.
Exemple
On ajoute 25 à l’exemple initial :
page 4
A.B.R.
22
13
28
18
11
25
31
16
Programme
let rec ajouter x = function
|Vide -> Noeud(Vide,x,Vide)
|Noeud(g,n,d) as arbre -> if (n = x)
then arbre
else if (x < n)
then Noeud((ajouter x g),n,d)
else Noeud(g,n,(ajouter x d));;
La complexité est
proportionnelle au nombre d’appels reccursifs effectués donc est
de la forme O h où h est la hauteur de l’arbre.
II.3 Insertion à la racine
On peut aussi choisir d’insérer un nouveau nœud à la racine.
Pour cela il faut couper l’arbre en deux parties regroupant les nœuds inférieurs à la
nouvelle clé et ceux qui sont supérieurs.
De plus on va essayer de le faire avec un coût minimum c’est–à–dire fonction de la
hauteur et non de la taille.
Le dernier arbre construit :
A.B.R.
page 5
22
13
28
18
11
31
25
16
est coupé par 17 en
22
13
28
18
11
31
25
16
on reconstruit deux arbres :
22
13
11
page 6
18
16
28
25
31
A.B.R.
puis on ajoute 17
17
13
11
22
16
18
28
25
31
Algorithme de coupure
On obtient les parties gauche et droite récursivement
• Si la clé de coupure est inférieure à la clé de la racine alors la partie gauche est
la partie gauche du découpage du fils gauche (il n’y a aucune clé inférieure dans
le fils droit).
• Sinon la partie gauche est l’arbre obtenu en remplaçant le fils droit de l’arbre
initial par la partie gauche du découpage du fils droit.
• De même pour la partie droite
let rec decouper x = function
|Vide -> (Vide,Vide)
|Noeud(g,n,d) -> if (n = x)
then (g,d)
else if (x < n)
then let (gg,gd) = decouper x g in
(gg,Noeud(gd,n,d))
else let (dg,dd) = decouper x d in
(Noeud(g,n,dg),dd);;
Reconstruction
let ajouter_racine x a =
let (g,d)= decouper x a in Noeud(g,x,d);;
On a conservé la possibilité d’ajouter une clé déjà existante, elle est alors éliminée
des arbres gauche et droite. Cela aura comme effet de déplacer cette clé à la racine.
A.B.R.
page 7
II.4 Application : tri
On peut utiliser les arbres binaires pour effectuer un tri.
• On insère (dans le désordre) les éléments à trier dans un arbre à partir de l’arbre
vide.
• On lit l’arbre dans un parcours infixe : la liste est triée.
Complexité
Sion admet
que la hauteur moyenne d’un arbre binaire de taille n est un
O ln(n) alors le temps moyen d’insertion de la k–ième valeur dans l’arbre bi
naire est O ln(k) donc la complexité moyenne de la création du tableau est
n
P
O ln(k) = O n ln(n) .
k=1
Comme le parcours infixe se fait en temps linéaire, O n , le tri sera fait, en
moyenne, avec une complexité de O n ln(n) .
II.5 Suppression
La suppression d’un nœud demande de conserver la structure ordonnée.
Il est relativement facile d’imaginer comment supprimer une feuille : on la remplace par l’arbre vide.
Le cas d’un nœud interne n est plus délicat. On se ramène au cas de la racine car il
suffira de remplacer le sous–arbre dont n est la racine par l’arbre diminué.
Choix d’une racine
Si on ôte la racine d’un sous–arbre il faut choisir un nœud à placer à la racine. Pour
le faire sans trop de modification on va choisir un nœud du fils gauche et la placer
à la racine. La valeur de sa clé sera inférieure à toutes les clés du fils droit et pour
majorer les clés du fils gauche il faut choisir le maximum dans le fils gauche.
On peut aussi choisir le minimum du fils droit.
On va donc opérer en 3 étapes
calculer la clé maximale du fils gauche,
on cherche récursivement le fils droit sans fils droit
enlever le nœud correspondant
si T 0 est le sous–arbre du fils gauche dont la clé est maximale alors il n’a pas de
fils droit (qui, sinon, aurait des clés supérieures) ; le fils gauche de T 0 remplace
T0
reconstuire l’arbre.
Si la racine n’avait pas de fils gauche le nouvel arbre serait simplement le fils droit.
page 8
A.B.R.
17
9
22
6
16
18
28
12
11
14
9
22
6
16
12
11
A.B.R.
31
25
18
28
25
31
14
page 9
16
9
22
6
18
12
11
14
28
25
31
let rec max = function
|Vide -> raise(Failure "Arbre vide")
|Noeud(_,n,Vide) -> n
|Noeud(_,_,d) -> max d;;
let rec supprMax = function
|Vide -> raise(Failure "Arbre vide")
|Noeud(g,n,Vide) -> g
|Noeud(g,n,d) -> Noeud(g,n,supprMax d);;
let enleverRacine = function
|Vide -> Vide
|Noeud(g,n,Vide) -> g
|Noeud(g,n,d) ->
let r = max g in
Noeud(supprimerMax g,r,d);;
On peut donc maintenant écrire la suppression d’un nœud.
let rec enlever x = function
|Vide -> Vide
|Noeud(g,n,d) as arbre ->
if (n = x)
then enleverRacine arbre
else if (x < n)
then Noeud((enlever x g),n,d)
else Noeud(g,n,(enlever x d));;
page 10
A.B.R.
III Exercices
III.1 Parcours de recherche
On suppose que tous les nombres de 1 à 800 sont les clés d’un arbre binaire de recherche.
Parmi le suites suivantes lesquelles ne peuvent pas être les clés d’un parcours de
recherche de 417 ?
• 222, 457, 122, 217, 417
• 545, 215, 333, 401, 422, 417
• 601, 403, 555, 408, 523, 411, 466, 417
• 601, 403, 555, 408, 563, 411, 466, 417
• 901, 403, 555, 411, 466, 417
III.2 Reconstruction
Prouver que la liste des éléments d’un arbre binaire de recherche lu en parcours
préfixe permet de reconstruire l’arbre.
III.3 Fusion
Écrire une fonction Fusion a b qui joint deux arbres binaires de recherche ; on
pourra utiliser les fonctions de coupure d’un arbre binaire de recherche.
III.4 Successeurs et prédécesseurs
Le successeur (resp. prédécesseur) d’un nœud de clé k est le nœud de l’arbre dont la
clé suit (resp. précéde) k dans le parcours infixe.
Montrer que si un nœud d’un arbre binaire de recherche a deux fils alors son prédécesseur n’a pas de fils droit et son successeur n’a pas de fils gauche.
Montrer que si n est le successeur de p dans le parcours croissant d’un ABR alors
soit n est le plus petit élément du fils droit de p soit p est le plus grand élément du
fils gauche de n.
III.5 Nombre de constructions
Déterminer tous les ordres possibles d’insertion de clés à partir de l’arbre vide qui
donnent l’arbre suivant par adjonction aux feuilles
A.B.R.
page 11
6
8
4
7
2
3
Plus généralement montrer que le nombre de permutations des clés d’un arbre T , de
taille n, qui engendrent l’arbre par adjonctions successives aux feuilles est n! divisé
par le produit des tailles de tous les sous–arbres (y compris T ) de T .
III.6 Vérification
Écrire une fonction qui teste si l’arbre binaire passé en paramètre est un arbre binaire de recherche.
III.7 Complexité moyenne d’une recherche
On considère les arbres binaires de recherche construits en ajoutant à l’arbre vide
les nœuds de clé 1 à n dans le desordre.
On suppose que les n! ordres d’insertion sont équiprobables.
On cherche la complexité moyenne, en nombre de comparaisons, de la recherche
d’une clé dans l’arbre : an .
•
•
•
•
Montrer que la probabilité que la clé de la racine soit i est n1 . On note ai,n la
complexité moyenne, en nombre de comparaisons, de la recherche d’une clé
dans un arbre dont la racine a la clé i.
i−1 1
n−i
Prouver que ai,n = (ai−1 + 1)
+ + (an−i + 1)
.
n
n
n
n−1
2 X
En déduire que an = 1 + 2
iai
n i=1
1
puis que an = 2 (n2 − 1)an−1 + 2n − 1
n
(On pourra calculer n2 an − (n − 1)2 an−1 ).
Prouver que an = O ln(n)
n
(On pourra poser un = n+1
an ).
page 12
A.B.R.
III.8 Rang et rang local
Les arbres binaires de recherche considérés sont étiquetés par des entiers distincts.
On appelle rang d’un nœud de clé k, le nombre de nœuds de clé inférieure ou égale
à k, et rang local d’un nœud son rang dans le sous-arbre dont il est racine.
On définit les arbres binaires en Caml par:
type ab = Vide
|N of int * int * ab * ab;;
Dans N(e, r, g, d), e est l’étiquette, r est son rang local et g et d sont les deux sousarbres gauche et droit.
i. On suppose qu’un arbre binaire est construit, mais que les champs r de chaque
nœud contiennent 0.
Écrire une fonction Caml qui prend l’arbre en paramètre et renvoie le même
arbre mais avec les champs r remplis correctement.
ii. On suppose maintenant que r est bien le rang local. Donner un algorithme qui, à
un entier k contenu dans l’arbre associe le rang du nœud de clé k. L’algorithme
doit être en O(h) où h est la hauteur de l’arbre.
iii. Soit n la taille de l’arbre, et m un entier compris entre 1 et n. Donner un algorithme qui renvoie la clé du nœud de rang m.
A.B.R.
page 13