Plan Plan Arbres et forêts Algorithme union-find Ordres sur les mots, parcours d'arbres binaires Arbres binaires de recherche Arbres et forêts Algorithme union-find Ordres sur les mots, parcours d'arbres binaires Arbres binaires de recherche Amphi Amphi Rappel : une définition récursive des arbres Forêts Un arbre est un ensemble fini de nœuds, tel que : () Il existe un nœud particulier appelé racine, () Les nœuds restants sont partitionnés en ensembles qui sont eux mêmes des arbres. 4 T = (, (, (), (6)), (, (), (), ()), (4)) Une forêt est un ensemble fini d'arbres 4 4 6 6 Amphi Amphi 4
Implantation des forêts par des tableaux Plan 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) 4 Arbres et forêts Algorithme union-find Ordres sur les mots, parcours d'arbres binaires Arbres binaires de recherche 4 6 4 6 4 pere 4 4 4 4 Amphi Amphi 6 Partitions Données : une partition de l'ensemble,,..., n- Trouver la classe d'un élément (find). Faire l'union de deux classes (union).,,, 6, 4,, 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 =,,,, 6,,, 4,, classe classe Trouver : O() Union : O(n) 4 6 Amphi Amphi
Deuxième solution : forêts Union P =,,,, 6,,, 4,, Union des classes de et de 6 4 Trouver : O(h) Union : O(h) h = hauteur 4 pere 4 6 4 4 6 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) Amphi Amphi Union des classes de et de Union Union des classes de et de Union 4 4 4 6 4 6 Amphi Amphi
Union Union Union des classes de et de Union des classes de et de 4 4 4 6 4 6 Amphi Amphi 4 class UnionFind static final int n = 6; static int[] pere = new int[n]; static int trouver(int x) while (pere[x]!= x) x = pere[x]; return x; Trouver static void union(int x, int y) int r = trouver(x); int s = trouver(y); if (r!= s) pere[r] = s; Union Amphi Amphi 6
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). 4 4 Taille Taille 6 Amphi 6 Union pondérée static int[] taille = new int[n]; // initialisé à (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]; Amphi Union pondérée Lemme. La hauteur d'un arbre à n nœuds créé par union pondérée est! +!log n". Preuve. Par récurrence sur n. Pour n =, 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! m! n/, sa hauteur est majorée par max( +!log (n-m)", +!log (m)") Comme log (m)! log (n/) = log (n) -, et log (n - m)! log (n), la hauteur est majorée par +!log n". n-m Amphi 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. 4 4 Amphi Trouver
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; Complexité Théorème (Tarjan) Avec l'union pondérée et la compression des chemins, une suite de n- "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)! pour m " n et n < 66. Toutefois, Tarjan a montré que l'algorithme précédent n'est pas linéaire! Amphi Amphi Définition précise de a Soit A : N x N --> N la fonction définie par A(i, ) = pour i ", A(, j) = j pour j ", A(i+, j+) = A(i, A(i+, j)) pour i, j ". Par exemple, A(, ) = 66 et 66 fois A(,4) = Soit a la fonction définie pour m " n par a(n, m) = min x " A(x, #4m/n$) > log n En fait, a(n, m)! pour m " n et n < 66. Amphi Deux points d'un réseau sont-ils connectés? Les points (, ), (, ), (, 4), (6, ), (, ), (, 6) et (, ) sont connectés. Est-ce que 4 et 6 sont connectés? Union-Find résout ce problème efficacement! 6 4 Amphi 4
Equivalence de deux automates déterministes Equivalence de deux automates déterministes On calcule (par Union-Find!) la plus petite relation d'équivalence ~ sur Q % Q telle que les états initiaux soient équivalents ( ~ ) 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 n et n états et l'alphabet a k lettres, algorithme en O(kna(kn, n)), où n = n + n. Donc quasi-linéaire en kn Amphi Amphi 6 Plan Mots Arbres et forêts Algorithme union-find Ordres sur les mots, parcours d'arbres binaires Arbres binaires de recherche Un alphabet est un ensemble de lettres :,, a, b, c, d, r Un mot est une suite de lettres :, abracadabra La longueur d'un mot u (notée u ) est le nombre de lettres de u : =, abracadabra = Le mot vide, de longueur, est noté e Amphi Amphi
Ensembles préfixiels Codage d'un arbre binaire 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,,,, e,,,,, sont des ensembles préfixiels. Code : e,,,,,,,,, Un ensemble fini de mots est préfixiel si et seulement si c'est le code d'un arbre binaire. Amphi Amphi L'ordre lexicographique Au départ, on fixe un ordre total sur l'alphabet : <, 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. u v Amphi u v p p a b v' u' 4 6 Codage du parcours préfixe Parcours préfixe (CGD) :,, 4,,,,,, 6, Codage : e,,,,,,,,, Le parcours préfixe est codé par l'ordre lexicographique. Amphi
4 6 Codage du parcours postfixe Parcours postfixe (GDC) :,, 4,,,, 6,,, Codage :,,,,,,,,, e Le parcours postfixe est codé par l'ordre opposé de l'ordre lexicographique obtenu en prenant <. Amphi 4 6 Le parcours infixe donne les nombres en ordre croissant! Codage du parcours infixe Infixe (GCD) :, 4,,,,,, 6,,,,,,,, e,,, Nombres (en binaire). = /6,.= /6,. = /6,.= 4/6,. = /6,. = 6/6,. = /6,. = /6,. =/6,.= 4/6 Amphi 4 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 Amphi 4 6 Parcours en largeur En largeur :,,, 4,, 6,,,, e,,,,,,,,, Le parcours en largeur est codé par l'ordre des mots croisés. Remarque : la suite,,,,,,,,, est croissante. Amphi 6
Plan Arbre binaire de recherche Arbres et forêts Algorithme union-find Ordres sur les mots, parcours d'arbres binaires Arbres binaires 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. 4 6 Amphi Amphi 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. 4 6 4 6 Exemple : prédécesseur de 6 Amphi class Arbre Arbre filsg; int contenu; Arbre filsd; Les arbres binaires en Java Arbre(Arbre a, int v, Arbre b) filsg = a; contenu = v; filsd = b; Amphi 4
Chercher un élément static Arbre chercher(int x, Arbre a) if (a == null x == a.contenu) if (x < a.contenu) return chercher(x, a.filsg); return chercher(x, a.filsd); Retourne l'arbre dont la racine contient x, ou null. 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; Amphi 4 Amphi 4 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); Amphi 4 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)); Amphi 44
(a) Le nœud est une feuille : on l'élimine. Supprimer () Supprimer () (b) Le nœud s a un seul fils t : on élimine s et on «remonte» t. 4 6 4 6 4 6 s t 4 t Amphi 4 Amphi 46 Supprimer () (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. t s 4 6 6 Amphi 4 4 Supprimer (destructive) static Arbre supprimer(int x, Arbre a) if (a == null) 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); Amphi 4
Supprimer la racine (récursivité croisée) Dernier descendant 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); static Arbre dernierdescendant(arbre a) if (a.filsd == null) return dernierdescendant(a.filsd); 4 6 Amphi 4 Amphi Le dernier descendant du nœud est le nœud 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; Amphi Avant dernier descendant static Arbre avantdernierdesc(arbre a) // a.filsd!= null while (a.filsd.filsd!= null) a == a.filsd; 4 6 L'avant dernier descendant du nœud est le nœud Amphi
Supprimer la racine (version itérative) Supprimer la racine (version itérative) b a 4 4 b a 4 b a 4 4 p 6 4 p 6 f cas (i) cas (ii) Amphi Amphi 4 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 -, arbres bicolores. Hauteur moyenne La hauteur moyenne d'un arbre binaire à n nœuds est en O(n / ), 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. Amphi Par exemple les deux suites,, et,, produisent le même arbre binaire de recherche. Amphi 6