TD d`algorithmique Arbres - Von Koch

publicité
TD d’algorithmique
Arbres
1 / Exemples
Arbre généalogique
source : http://derniersvalois.canalblog.com/archives/2007/04/30/5615361.html
Arbre binaire de recherche : Alphabet Morse
source : http://pauillac.inria.fr/~maranget/X/TC/X.97/TD-6/enonce.html
Arbre lexicographique
E
C
I
E
I
L
E
L
C
L
L
I
E
I
I
L
I
L
C
E
L
C
E
C
E
L
C
I
I
C
C
E
E
C
Arbre de jeu
source : http://www.tony-lambert.fr/these/these.html
Arbre binaire de codage : Compactage de Huffmann
source : http://replicateur.free.fr/site%20tpe%202004/pages/huffman.html
Arbre d'expression algébrique
2 / Définitions
Arbre : structure ordonnée de "noeuds" où chaque noeud a 0, 1 ou plusieurs successeur et chaque noeud a un et un seul
prédécesseur, sauf un seul noeud qui n'en a aucun
racine
A
ancêtres de R
père de H
B
C
D
profondeur 1
fils de D
E
K
F
G
L
M
H
N
O
I
J
P
profondeur 2
Q
feuilles
R
descendants de C
S
Tout noeud peut être considéré comme un arbre dont il est la racine
Exemple dans l'arbre précédent si on identifie :
le noeud G à l'arbre
le noeud E à l'arbre
N
U
et le noeud D à l'arbre
E
G
M
T
K
O
D
H
L
R
S
I
J
P
Q
T
alors l'arbre peut s'écrire plus simplement
A
B
E
C
F
G
D
U
Arbre binaire : Chaque noeud a au plus deux fils
Remarque : Si la hauteur est n, l'arbre a au plus 1 2 4 ... 2n
2n
1
1 noeuds
1
11
12
fils droit de 12
fils gauche de 12
111
112
121
122
1222 n'a
pas de fils
121 n'a qu'un
fils gauche
1111
1112
1121
11211
11212
1122
11221
1221
1211
11222
12211
1222
12212
Chaque noeud est caractérisé par une une valeur (appelée clé, ou étiquette) et ses fils éventuels (c'est à dire le sous-arbre
de gauche et le sous-arbre de droite..
Exemple dans l'arbre binaire précédent,
le noeud 112 est caractérisé par
112 , 1121 , 1122 ,
le noeud 121 est caractérisé par
121 , 1211 ,
et le noeud 1222 par 1222 ,
arbre vide
Un arbre binaire est donc :
 soit l'arbre vide
 soit la donnée d'une clé et de 2 arbres binaires
arbre vide
,
arbre vide
3 / Implémentation en Python d'un arbre binaire
Dans la plupart des langages, l'implémentation des arbres se fait à l'aide de 'pointeurs' , c'est-à-dire de variables qui
contiennent l'adresse mémoire d'autres variables.
En langage Python, on utilisera une implémentation utilisant des listes :
Définition récursive :
Un arbre binaire est
soit la liste vide
soit une liste composée d'une clé et de 2 arbres binaires
exemples
arbre0=[]
arbre1=[1,[],[]]
arbre2=[2,arbre0,arbre0]
arbre3=[3,arbre2,arbre1]
arbre4=[4,arbre3,arbre1]
>>> [ 4 ,
[3, [2,[],[]],[1,[],[]]], [1,[],[]]]
4 / Primitives
Quelle que soit l'implémentation et le langage utilisé, on doit pouvoir disposer pour un arbre binaire des fonctions
élémentaires suivantes appelées primitives :
créer un arbre vide, ou à partir d'une clé et de 2 arbres
déterminer si un arbre est vide
déterminer le fils gauche d'un noeud
(c'est à dire en fait le sous-arbre gauche de l'arbre dont la racine est le noeud considéré . Il peut être vide)
déterminer le fils droit d'un noeud
déterminer la clé d'un noeud
changer la clé, le fils gauche ou le fils droit de la racine d'un arbre
Exercice. Coder en python les fonctions suivantes (on suppose que A est un arbre). Les tester
def
def
def
def
arbre_vide():
arbre(val,A,B):
est_vide(A):
fils_gauche(A):
#
#
#
#
retourne
retourne
retourne
retourne
un arbre vide
un arbre de clé 'val' et de fils A et B
vrai si l'arbre est vide, faux sinon
le fils gauche de la racine de l'arbre
# (si A non vide)
def fils_droit(A):
# retourne le fils droit de la racine de l'arbre
def cle(A):
# retourne la clé de la racine de l'arbre
def change_cle(A,valeur):
# retourne l'arbre A dans lequel la clé de
# la racine est remplacée par 'val'
def change_fils_gauche(A,B): # retourne l'arbre A dans lequel le fils gauche
def change_fils_droit(A,B):
# de la racine est remplacé par l'arbre 'B'
# idem
Désormais on n'utilisera plus de liste pour travailler sur des arbres, mais seulement les primitives ci-dessus.
Exercice. Coder en python les fonctions suivantes (on suppose que A et B sont des arbres)
def est_fils_de(A,B):
# vrai si A est un fils de B, faux sinon
def est_pere_de(A,B):
# vrai si A est le père de B, faux sinon
def est_ancetre_de(A,B):
# vrai si A est un ancetre de B, faux sinon
(faire une fonction récursive)
def hauteur(A): # donne la hauteur de l'arbre
(faire une fonction récursive)
def est_cle_de(x,A):
# vrai si x est la clé d’un nœud de A, faux sinon
(faire une fonction récursive)
Exercice  Coder en python une fonction affichant un arbre comme sur l'exemple suivant
affiche(arbre4)
4
On pourra par exemple définir une fonction récursive
|--- 3
en ajoutant un paramètre qui donne le nombre
|--- 2
d'espaces à écrire avant la clé de la racine
|--- 1
def affiche(A,nbespaces):
|--- 1
...
5 / Parcours en profondeur
On commence à la racine, et on parcourt l'arbre de haut en bas, puis vers la droite
Dans cet exemple, on obtient la liste de clés : (compléter)
1 , 11 , 111 , 1111 , 1112 , 112 , 1121 ,
1
11
12
111
1111
112
1112
11211
121
1122
1121
11212
11221
122
1221
1211
11222
1222
12212
12211
Algorithme récursif :
Pour parcourir l'arbre, on note la clé de la racine, on parcourt l'arbre fils_gauche, puis l'arbre fils-droit.
Exercice. Coder en python la fonction récursive parcours_en_profondeur(A) qui retourne la liste des
clés, dans l'ordre du parcours.
Remarque : comme dit plus haut, on ne travaille pas sur des listes pour les arbres. On n'utilise que les primitives.
Exercice. Coder en python une fonction qui retourne la liste des (clés des ) descendants de B dans
l'arbre A.
Exercice  Coder en python qui retourne la liste des ancêtres de B dans l'arbre A.
Parcours en largeur
On parcourt tous les noeuds situés à une même profondeur, de gauche
à droite, avant de passer aux noeuds de la profondeur suivante.
A
début
B
Algorithme
Créer une liste contenant l'arbre (la racine)
Tant que cette liste n'est pas vide, enlever le premier élément,
noter sa clé, et ajouter à la liste son fils gauche et son fils droit
(s'ils existent)
Dans l'exemple ci-contre, on obtient la liste de clés
C
D
G
E
H
I
F
J
K
L
A, B, C , D, E , F , G, H , I , J , K , L, M , R
Exercice Coder en python la fonction
parcours_en_largeur(A) qui retourne la liste des clés.
M
R
fin
6 / Arbres binaires de recherche
Exercice  Soit une liste triée. Créer un arbre binaire dont les clés sont les termes de la liste, et tel
que la clé de chaque noeud soit supérieure aux clés du fils gauche et de tous ses descendants, et
inférieure aux clésdu fils droit et de tous ses descendants.
Par exemple, pour la liste [ a , c , e , f , h , k , l , r , s , t , v , w , z ] on veut obtenir l'arbre suivant :
r
clés inférieures à ' f '
f
c
a
t
k
e
w
Clés supérieures à ' f '
h
l
s
z
v
Un arbre vérifiant la propriété précédente est appelé arbre binaire de recherche.
Pour chercher une clé dans un tel arbre, on procède récursivement :
Si l'arbre est vide, la clé ne s'y trouve pas.
Sinon on regarde si la clé se trouve à la racine
Sinon on regarde si la clé est avant la clé de la racine.
Dans ce cas on la recherche dans le fils gauche, sinon on recherche dans le fils droit.
Exercice. Coder en python la fonction récursive recherche( cle, ABR ) qui retourne vrai si la clé est
dans l'arbre binaire de recherche ABR, faux dans le cas contraire.
Exercice. Étudier la complexité d'une telle recherche : nombre de comparaisons au maximum pour
trouver une clé dans un arbre binaire de recherche. Montrer que cette complexité dépend
principalement de la hauteur de l'arbre
Il est donc préférable que l'arbre soit "équilibré", c'est-à-dire que tous les sous-arbres aient "à peu près" la même hauteur.
Suggestion d'exercices :
Dans un arbre binaire de recherche :
Insérer une clé
supprimer une clé
recherche la chaine de longueur maximale reliant la racine aux différentes feuilles
améliorer l'"équilibre" de l'arbre
Proposer une démarche à structure d'arbre pour rechercher un mot dans un dictionnaire en moins de
20 comparaisons ( le Larousse contient environ 60 000 entrées)
Téléchargement