Amphi 5 1
Plan
• Arbres et forêts
• Algorithme union-find
• Ordres sur les mots, parcours d'arbres binaires
• Arbres binaires de recherche
Amphi 5 2
Plan
• Arbres et forêts
• Algorithme union-find
• Ordres sur les mots, parcours d'arbres binaires
• Arbres binaires de recherche
Rappel : une définition récursive des arbres
Un arbre est un ensemble fini de nœuds, tel que : (1) Il existe un nœud particulier appelé racine, (2) Les nœuds restants sont partitionnés en ensembles qui sont eux mêmes des arbres.
5 7 8
4 2
1
3
6 9
T =
(
1,{
(2, {(5), (6)}), (3, {(7), (8), (9)}), (4)} )
Forêts
Une forêt est un ensemble fini d'arbres
4 11 10
7 3
14
9
13
12 6 0
8
2 5
1
Amphi 5 5
Implantation des forêts par des tableaux
On représente la forêt par un tableau pere tel que pere[s] = père de s (si s est une racine, pere[s] = s)
0 1 2 3 4 5 6 7 8 9
4 11 10
7 3
14
9
13
12 6 0
8
2 5
1
pere 2 8 5 14 3 5 2 14 8 14 9 9 3 9 14
1011121314
Amphi 5 6
Plan
• Arbres et forêts
• Algorithme union-find
• Ordres sur les mots, parcours d'arbres binaires
• Arbres binaires de recherche
Partitions
Données : une partition de l'ensemble {0, 1, ..., n-1}
• Trouver la classe d'un élément (find).
• Faire l'union de deux classes (union).
2, 5, 8 0, 6 1 3, 4, 7, 9
0 1 2 3
Première solution : tableau
On représente la partition par un tableau classe tel que classe[i] soit la classe de l'élément i.
P =
{
{2, 5, 8}, {0, 6}, {1}, {3, 4, 7, 9}}
classe 0 1 2 3
1 2 0 3 3 0 1 3 0 3
Trouver : O(1) Union : O(n)
0 1 2 3 4 5 6 7 8 9 classe
Amphi 5 9
Deuxième solution : forêts
P =
{
{2, 5, 8}, {0, 6}, {1}, {3, 4, 7, 9}}
0 1 5 4 7 5 0 7 2 7
0
8 2 5
9 4
7
3 6
1
On représente la forêt par un tableau pere tel que pere[s] = père de s (si s est une racine, pere[s] = s)
Trouver : O(h) Union : O(h) h = hauteur
0 1 2 3 4 5 6 7 8 9 pere
Amphi 5 10
Union
Union des classes de 11 et de 2
4 11 10
7 3
14
9
13
12 6 0
8
2 5
1
Union
Union des classes de 11 et de 2
4 11 10
7 3
14
9
13
12 6 0
8
2 5
1
Union
Union des classes de 11 et de 2
4 11 10
7 3
14
9
13
12 6 0
8
2 5
1
Amphi 5 13
Union
Union des classes de 11 et de 2
4 11 10
7 3
14
9
13
12 6 0
8
2 5
1
Amphi 5 14
Union
Union des classes de 11 et de 2
4 11 10
7 3
14
9
13 12
6 0
2 5
8
1
Trouver
class UnionFind {
static final int n = 16;
static int[] pere = new int[n];
static int trouver(int x) {
while (pere[x] != x) x = pere[x];
return x;
} }
Union
static void union(int x, int y) {
int r = trouver(x);
int s = trouver(y);
if (r != s) pere[r] = s;
}
Amphi 5 17
Union pondérée
Règle : Lors de l'union, la racine de l'arbre de moindre taille devient fils de la racine de l'arbre de plus grande taille (la taille est le nombre de nœuds).
6
10
8 4
9 5
1
11 7
3
2
12
13 4
Taille 7 Taille 6
Amphi 5 18
Union pondérée
static int[] taille = new int[n];
// initialisé à 1 (méthode omise)
static void unionPonderee(int x, int y){
int r = trouver(x);
int s = trouver(y);
if (r == s) return;
if (taille[r] > taille[s]) { pere[s] = r;
taille[r] = taille[r] + taille[s];
} else {
pere[r] = s;
taille[s] = taille[r] + taille[s];
} }
Union pondérée
Lemme. La hauteur d'un arbre à n nœuds créé par union pondérée est ! 1 + !log2 n".
Preuve. Par récurrence sur n. Pour n = 1, vrai.
Si un arbre est obtenu par union pondérée d'un arbre à m nœuds et d'un arbre à (n-m) nœuds, avec 1 ! m ! n/2, sa hauteur est majorée par max(1 + !log2(n-m)", 2 + !log2(m)")
Comme log2(m) ! log2(n/2) = log2(n) - 1, et log2(n - m) ! log2(n), la hauteur est majorée par 1 + !log2 n".
n-m m
Trouver, avec compression des chemins
Principe. On remonte du nœud x à sa racine r, puis on refait le parcours en faisant de chaque nœud rencontré un fils de r.
10
8 4
5 9 1
11 7
3
2
10
8
4 9
5
1
11 7
3
2
Trouver 10
Amphi 5 21
Trouver avec compression des chemins
static int trouverAvecCompression(int x) {
int r = trouver(x);
while (x != r) {
int y = pere[x];
pere[x] = r; // x devient fils de r x = y;
}
return r;
}
Amphi 5 22
Complexité
Théorème (Tarjan) Avec l'union pondérée et la compression des chemins, une suite de n-1
"unions" et de m "trouver" (m " n) se réalise en temps O(n + m.a(n, m)), où a est une fonction quasi-constante.
En fait, a(n,m) ! 2 pour m " n et n < 265536.
Toutefois, Tarjan a montré que l'algorithme précédent n'est pas linéaire !
Définition précise de a
Soit A : N x N --> N la fonction définie par A(i, 0) = 1 pour i " 1, A(0, j) = 2j pour j " 0, A(i+1, j+1) = A(i, A(i+1, j)) pour i, j " 0.
Par exemple, A(2, 5) = 265536 et 2
A(3,4) = 22
Soit a la fonction définie pour m " n par
a(n, m) = min { x " 1 | A(x, #4m/n$) > log n } En fait, a(n, m) ! 2 pour m " n et n < 265536.
…
}
65536 foisDeux points d'un réseau sont-ils connectés ?
Les points (1, 3), (2, 3), (5, 4), (6, 3), (7, 5), (1, 6) et (7, 0) sont connectés.
Est-ce que 4 et 6 sont connectés ?
Union-Find résout ce problème efficacement !
0 2
5 4
7 3
6 1
Amphi 5 25
Equivalence de deux automates déterministes
Amphi 5 26
Equivalence de deux automates déterministes
On calcule (par Union-Find !) la plus petite relation d'équivalence ~ sur Q1 % Q2 telle que les états initiaux soient équivalents (1 ~ 5) et telle que
si p ~ q et si a est une lettre, alors p.a ~ q.a.
On vérifie ensuite que si p ~ q et si p est un état final, alors q est un état final.
Complexité. Si les automates ont n1 et n2 états et l'alphabet a k lettres, algorithme en O(kna(2kn, n)), où n = n1 + n2. Donc quasi-linéaire en kn
Plan
• Arbres et forêts
• Algorithme union-find
• Ordres sur les mots, parcours d'arbres binaires
• Arbres binaires de recherche
Mots
Un alphabet est un ensemble de lettres : {0, 1}, {a, b, c, d, r}
Un mot est une suite de lettres : 01101, abracadabra
La longueur d'un mot u (notée |u|) est le nombre de lettres de u :
|01101| = 5, |abracadabra| = 11
Le mot vide, de longueur 0, est noté e
Amphi 5 29
Ensembles préfixiels
Concaténation de deux mots :
u = abra, v = cadabra, uv = abracadabra
Un mot p est préfixe (propre) d'un mot u s'il existe un mot v (non vide) tel que u = pv.
e, abr, abrac sont des préfixes propres de abraca.
Un ensemble de mots P est préfixiel si tout préfixe d'un mot de P est lui-même dans P.
{e, 1, 10, 11}, {e, 0, 00, 01, 000, 001} sont des ensembles préfixiels.
Amphi 5 30
Codage d'un arbre binaire
0 1
0
0
0
0 1
1 1
Code : {e, 0, 1, 00, 01, 10, 11, 010, 101, 110}
Un ensemble fini de mots est préfixiel si et seulement si c'est le code d'un arbre binaire.
L'ordre lexicographique
Au départ, on fixe un ordre total sur l'alphabet : 0 < 1, a < b < c < d < r
L'ordre lexicographique (ou du dictionnaire) est défini par u <lex v si et seulement si
- u est un préfixe de v ou
- u = pau' et v = pbv' où p est un mot et a et b sont des lettres telles que a < b.
p p
a b
u' v' u
v u
v
Codage du parcours préfixe
9 10 8
4 5 6 7
2 3
1
Parcours préfixe (CGD) : 1, 2, 4, 8, 9, 5, 10, 3, 6, 7 Codage : e, 0, 00, 000, 001, 01, 010, 1, 10, 11
0 1
0
0 0
0
1 1
1
Le parcours préfixe est codé par l'ordre lexicographique.
Amphi 5 33
Codage du parcours postfixe
9 10 8
4 5 6 7
2 3
1
Parcours postfixe (GDC) : 8, 9, 4, 10, 5, 2, 6, 7, 3, 1 Codage : 000, 001, 00, 010, 01, 0, 10, 11, 1 , e
0 1
0
0 0
0
1 1
1
Le parcours postfixe est codé par l'ordre opposé de l'ordre lexicographique obtenu en prenant 1 < 0.
Amphi 5 34
Codage du parcours infixe
Infixe (GCD) :
8, 4, 9, 2, 10, 5, 1, 6, 3, 7 000, 00, 001, 0, 010, 01, e, 10, 1, 11
9 10 8
4 5 6 7
2 3
1
0 1
0
0 0
0
1 1
1
Le parcours infixe donne les nombres en ordre croissant !
Nombres (en binaire) .0001 = 1/16, .001= 2/16, .0011 = 3/16, .01= 4/16, .0101 = 5/16, .011 = 6/16, .1 = 8/16, .101 = 9/16, .11 =12/16, .111= 14/16
L'ordre des mots croisés
L'ordre des mots croisés (shortlex en anglais) est défini par u <mc v si et seulement si
- |u| < |v| ou
- |u| = |v| et u <lex v.
Exemples
bar <mc car <mc barda <mc radar <mc abracadabra
Parcours en largeur
9 10 8
4 5 6 7
2 3
1
En largeur :
1, 2, 3, 4, 5, 6, 7, 8, 9, 10 e, 0, 1, 00, 01, 10, 11, 000, 001, 010
0 1
0
0 0
0
1 1
1
Le parcours en largeur est codé par l'ordre des mots croisés.
Remarque : la suite 1, 10, 11, 100, 101, 110, 111, 1000, 1001, 1010 est croissante.
Amphi 5 37
Plan
• Arbres et forêts
• Algorithme union-find
• Ordres sur les mots, parcours d'arbres binaires
• Arbres binaires de recherche
Amphi 5 38
Arbre binaire de recherche
Définition : Pour chaque nœud de contenu c, les nœuds du sous-arbre gauche ont un contenu < c et ceux du sous-arbre droit ont un contenu > c.
10 13
8 14 16 21
12 19
15
17
Propriétés des arbres binaires de recherche
• Le parcours infixe (GCD) ordonne les nœuds par contenu croissant.
• Si un nœud a un fils gauche, son prédécesseur est le nœud le plus à droite dans son sous-arbre gauche. Ce prédécesseur n'a pas de fils droit.
• Si un nœud a deux fils, son successeur n'a pas de fils gauche.
10 13
8 14 16 21
12 20
15
17 1
2 3
4 5
6
7
8 9
10
Exemple : prédécesseur de 6
Les arbres binaires en Java
class Arbre { Arbre filsG;
int contenu;
Arbre filsD;
Arbre(Arbre a, int v, Arbre b) {
filsG = a;
contenu = v;
filsD = b;
} }
Amphi 5 41
Chercher un élément
static Arbre chercher(int x, Arbre a) {
if (a == null || x == a.contenu) return a;
if (x < a.contenu)
return chercher(x, a.filsG);
return chercher(x, a.filsD);
}
Retourne l'arbre dont la racine contient x, ou null.
Amphi 5 42
Chercher (itératif)
static Arbre chercherI(int x, Arbre a) {
while (a != null && x != a.contenu) if (x < a.contenu)
a = a.filsG;
else
a = a.filsD;
return a;
}
Inserer (version destructive)
static Arbre inserer(int x, Arbre a) {
if (a == null)
return new Arbre(null, x, null);
if (x < a.contenu)
a.filsG = inserer(x, a.filsG);
else if (x > a.contenu)
a.filsD = inserer(x, a.filsD);
return a;
}
Inserer (version non destructive)
static Arbre inserer(int x, Arbre a) {
if (a == null)
return new Arbre(null, x, null);
if (x < a.contenu)
return new Arbre(inserer(x,
a.filsG), a.contenu, a.filsD);
else if (x > a.contenu) return new Arbre(a.filsG,
a.contenu, inserer(x, a.filsD));
return a;
}
Amphi 5 45
Supprimer (1)
(a) Le nœud est une feuille : on l'élimine.
10
8 14 16 21
12 20 15
17 13
10
8 14 16 21
12 20 15
17
Amphi 5 46
Supprimer (2)
(b) Le nœud s a un seul fils t : on élimine s et on
«remonte» t.
10
8 14 21
12 20 15
13 10
8 14 16 21
12 20 15
18 13
19 17
18
19 17
s t
t
Supprimer (3)
(c) Le nœud s a deux fils : on cherche son prédécesseur t (qui n'a pas de fils droit), on remplace le contenu de s par le contenu de t et on élimine t.
s
10
8 13 21
12 20 14
13 10
8 14 16 21
12 20 15
17
16
t
17
Supprimer (destructive)
static Arbre supprimer(int x, Arbre a) {
if (a == null) return a;
if (x == a.contenu)
return supprimerRacine(a);
if (x < a.contenu)
a.filsG = supprimer(x, a.filsG);
else // x > a.contenu a.filsD = supprimer(x, a.filsD);
return a;
}
Amphi 5 49
Supprimer la racine (récursivité croisée)
static Arbre supprimerRacine(Arbre a) {
if (a.filsG == null) // au plus un fils return a.filsD;
if (a.filsD == null) // idem return a.filsG;
Arbre f = dernierDescendant(a.filsG);
// f est le prédécesseur de a; voir (c) a.contenu = f.contenu; // remplace contenu, // f a au plus un fils
a.filsG = supprimer(f.contenu, a.filsG);
return a;
} Amphi 5 50
Dernier descendant
static Arbre dernierDescendant(Arbre a) {
if (a.filsD == null) return a;
return dernierDescendant(a.filsD);
}
13 10
8 14 16 22
12 20 15
17 21
Le dernier descendant du nœud 15 est le nœud 22
Supprimer la racine (itérative)
static Arbre supprimerRacine(Arbre a){
... // a possède deux fils Arbre b = a.filsG;
if (b.filsD == null) { // cas (i) a.contenu = b.contenu;
a.filsG = b.filsG;
} else { // cas (ii) Arbre p = avantDernierDesc(b);
Arbre f = p.filsD;
a.contenu = f.contenu;
p.filsD = f.filsG;
}
return a;
}
Avant dernier descendant
static Arbre avantDernierDesc(Arbre a) { // a.filsD != null
while (a.filsD.filsD != null) a == a.filsD;
return a;
}
13 10
8 14 16 21
12 19 15
17 20
L'avant dernier descendant du nœud 15 est le nœud 19
Amphi 5 53
Supprimer la racine (version itérative)
a
10
8 22 25
12 24 21
23
b
cas (i)
10 8
22 25 24 12
23
Amphi 5 54
Supprimer la racine (version itérative)
a
13 10
8 14 22 25
12 24 20
23
p b
16
18
cas (ii)
a
13 10
8 14 22 25
12 24 21
23
p b
16
20
18
f
Performances des arbres de recherche
Si h est la hauteur d'un arbre binaire de recherche, la recherche d'un élément, l'insertion et la suppression sont en O(h).
Le cas le pire est en O(n). Pour éviter ce cas, on utilise des arbres équilibrés : arbres AVL, arbres 2-3, arbres bicolores.
Hauteur moyenne
La hauteur moyenne d'un arbre binaire à n nœuds est en O(n1/2), quand tous les arbres binaires sont équiprobables.
La hauteur moyenne d'un arbre binaire de recherche à n nœuds est en O(log n). La moyenne est calculée sur les suites équiprobables de n entiers insérés dans un arbre initialement vide.
Par exemple les deux suites 2, 1, 3 et 2, 3, 1 produisent le même arbre
binaire de recherche. 1 3
2