AccueilđŸ‡«đŸ‡·Chercher

Union-find

En informatique, union-find est une structure de données qui représente une partition d'un ensemble fini (ou de maniÚre équivalente une relation d'équivalence). Elle a essentiellement deux opérations trouver et unir et est appelée union-find, suivant en cela la terminologie anglo-saxonne :

  • Find (trouver) : dĂ©termine la classe d'Ă©quivalence d'un Ă©lĂ©ment ; elle sert ainsi Ă  dĂ©terminer si deux Ă©lĂ©ments appartiennent Ă  la mĂȘme classe d'Ă©quivalence ;
  • Union (unir) : rĂ©unit deux classes d'Ă©quivalence en une seule.

Partition avec 8 classes (qui sont des singletons) obtenue avec MakeSet(1), 
, MakeSet(8).
Partition avec 3 classes disjointes obtenue aprĂšs Union(1, 2), Union(3, 4), Union(2, 5), Union(1, 6) et Union(2, 8).

Une autre opération importante, MakeSet, construit une classe d'équivalence contenant un seul élément, autrement dit un singleton.

Afin de définir ces opérations plus précisément, il faut choisir un moyen de représenter les classes. L'approche traditionnelle consiste à sélectionner un élément particulier de chaque classe, appelé le représentant, pour identifier la classe entiÚre.

Lors d'un appel, Find(x) retourne le représentant de la classe de x.

Implémentation utilisant des listes chaßnées

La solution la plus immédiate pour le problÚme des classes disjointes consiste à construire une liste chaßnée pour chaque classe.

On choisit alors l'Ă©lĂ©ment en tĂȘte de liste comme reprĂ©sentant.

MakeSet crée une liste contenant un élément. Union concatÚne les deux listes, une opération en temps constant. Malheureusement, l'opération Find nécessite alors un temps Ω(n) avec cette approche.

On peut y remĂ©dier en ajoutant Ă  chaque Ă©lĂ©ment d'une liste chaĂźnĂ©e un pointeur vers la tĂȘte de la liste ; Find est alors rĂ©alisĂ©e en temps constant (le reprĂ©sentant Ă©tant la tĂȘte de la liste). Cependant, on a dĂ©tĂ©riorĂ© l'efficacitĂ© de Union, qui doit maintenant parcourir tous les Ă©lĂ©ments d'une des deux listes pour les faire pointer vers la tĂȘte de l'autre liste, ce qui nĂ©cessite un temps Ω(n).

On peut améliorer cela en ajoutant toujours la plus petite des deux listes en queue de la plus longue, ce qui porte le nom d'heuristique de l'union pondérée. Cela nécessite de maintenir la longueur des listes au fur et à mesure des opérations. Avec cette solution, une séquence de m opérations MakeSet, Union et Find sur n éléments nécessite un temps O(m + nlog n).

Pour obtenir de meilleurs résultats, nous devons considérer une structure de données différente.

ImplĂ©mentation utilisant des forĂȘts

On considĂšre maintenant une structure de donnĂ©es dans laquelle chaque classe est reprĂ©sentĂ©e par un arbre dans lequel chaque nƓud contient une rĂ©fĂ©rence vers son nƓud pĂšre. Une telle structure de forĂȘt a Ă©tĂ© introduite par Bernard A. Galler et Michael J. Fisher en 1964[1], bien que son analyse dĂ©taillĂ©e ait attendu plusieurs annĂ©es.

Principe

Dans une telle forĂȘt, le reprĂ©sentant de chaque classe est la racine de l'arbre correspondant. Find se contente de suivre les liens vers les nƓuds pĂšres jusqu'Ă  atteindre la racine. Union rĂ©unit deux arbres en attachant la racine de l'un Ă  la racine de l'autre. Une maniĂšre d'Ă©crire ces opĂ©rations est la suivante :

 fonction MakeSet(x)
     x.parent := null
 fonction Find(x)
     si x.parent == null
        retourner x
     retourner Find(x.parent)
 fonction Union(x, y)
     xRacine := Find(x)
     yRacine := Find(y)
     si xRacine  yRacine
            xRacine.parent := yRacine

Sous cette forme naĂŻve, cette approche n'est pas meilleure que celle des listes chaĂźnĂ©es, car les arbres crĂ©Ă©s peuvent ĂȘtre trĂšs dĂ©sĂ©quilibrĂ©s. Mais elle peut ĂȘtre amĂ©liorĂ©e de deux façons.

Amélioration de l'union

La premiĂšre consiste Ă  toujours attacher l'arbre le plus petit Ă  la racine de l'arbre le plus grand, plutĂŽt que l'inverse. Pour Ă©valuer quel arbre est le plus grand, on utilise une heuristique appelĂ©e le rang[2] : les arbres contenant un Ă©lĂ©ment sont de rang zĂ©ro, et lorsque deux arbres de mĂȘme rang sont rĂ©unis, le rĂ©sultat a un rang plus grand d'une unitĂ©. Avec cette seule amĂ©lioration, la complexitĂ© amortie des opĂ©rations MakeSet, Union et Find devient au pire cas et au meilleur cas. Voici les nouveaux codes de MakeSet et Union :

 fonction MakeSet(x)
     x.parent := x
     x.rang   := 0
 fonction Union(x, y)
     xRacine := Find(x)
     yRacine := Find(y)
     si xRacine  yRacine
           si xRacine.rang < yRacine.rang
                 xRacine.parent := yRacine
           sinon
                 yRacine.parent := xRacine
                 si xRacine.rang == yRacine.rang
                         xRacine.rang := xRacine.rang + 1

Amélioration de find avec la compression de chemin

La seconde amĂ©lioration, appelĂ©e compression de chemin, consiste Ă  profiter de chaque Find pour aplatir la structure d'arbre ; en effet, chaque nƓud rencontrĂ© sur le chemin menant Ă  la racine peut ĂȘtre directement reliĂ© Ă  celle-ci ; car tous ces nƓuds ont le mĂȘme ancĂȘtre. Pour rĂ©aliser cela, on fait un parcours vers la racine, afin de la dĂ©terminer, puis un autre parcours pour faire de cette racine la mĂšre de tous les nƓuds rencontrĂ©s en chemin. L'arbre rĂ©sultant ainsi aplati amĂ©liore les performances des futures recherches d'ancĂȘtre, mais profite aussi aux autres nƓuds pointant vers ceux-ci, que ce soit directement ou indirectement. Voici l'opĂ©ration Find amĂ©liorĂ©e :

 fonction Find(x)
     si x.parent  x
        x.parent := Find(x.parent)
     retourner x.parent

Ces deux amĂ©liorations (fusion optimisĂ©e des racines et compression de chemin) sont complĂ©mentaires. Ensemble, elles permettent d'atteindre une complexitĂ© amortie en temps , oĂč est la rĂ©ciproque de la fonction et la fonction d'Ackermann dont la croissance est extrĂȘmement rapide[3]. L'entier vaut moins de 5 pour toute valeur en pratique[4]. En consĂ©quence, la complexitĂ© amortie par opĂ©ration est de fait une constante.

Il n'est pas possible d'obtenir un meilleur rĂ©sultat : Fredman et Saks ont montrĂ© en 1989 que mots en moyenne doivent ĂȘtre lus par opĂ©ration sur toute structure de donnĂ©es pour le problĂšme des classes disjointes. Dasgupta et al., dans leur livre d'algorithmique, prĂ©sente une analyse de complexitĂ© amortie en utilisant le logarithme itĂ©rĂ©[5].

Exemple

Exemple de forĂȘts obtenues aprĂšs union(a, b), union(c, d), union(a, c), find(d), union(b, e)[6].

La figure Ă  droite montre un exemple d'utilisation d'une structure de donnĂ©es union-find avec 6 Ă©lĂ©ments a, b, c, d, e, f. Au dĂ©but, la forĂȘt contient 6 arbres-racines : elle reprĂ©sente la relation d'Ă©quivalence oĂč chaque Ă©lĂ©ment est seul dans sa classe d'Ă©quivalence. L'opĂ©ration union(a, b) connecte (par exemple), le nƓud b Ă  la racine a. L'opĂ©ration union(c, d) connecte (par exemple), le noeud c Ă  la racine d. L'opĂ©ration union(a, c) connecte (par exemple) le nƓud c au nƓud a. L'opĂ©ration find(d) retourne la racine a, mais au passage, il connecte d directement Ă  la racine a (compression de chemins). L'opĂ©ration union(b, e) commence par chercher les racines respectives a et e. Comme le rang de a est 1 et celui de e est 0, on connecte donc l'arbre e Ă  l'arbre a.

Applications

Cette structure est souvent utilisĂ©e pour identifier les composantes connexes d'un graphe (voir l'article sur les algorithmes de connexitĂ© basĂ©s sur des pointeurs). Dans le mĂȘme esprit, elle est utilisĂ©e pour Ă©tudier la percolation, avec l'algorithme de Hoshen-Kopelman.

Union-Find est également utilisée dans l'algorithme de Kruskal, pour trouver un arbre couvrant de poids minimal d'un graphe. Elle est enfin utilisée dans les algorithmes d'unification[7] - [8].

Voir aussi

Articles connexes

  • La pile spaghetti, nom donnĂ© au type d’arbres mis en Ɠuvre dans la structure union-find.
  • L’union de deux ensembles mathĂ©matiques.
  • La partition d’un ensemble mathĂ©matique en sous-ensembles disjoints.

Lien externe

Notes et références

  1. Bernard A. Galler et Michael J. Fisher, « An improved equivalence algorithm, Communications of the ACM », ACM Digital Library, (consulté le ), Volume 7, Issue 5, pages 301-303.
  2. On pourrait l'appeler Â« profondeur Â», mais en prĂ©sence de compression de chemin, ce concept perd son sens.
  3. Robert Endre Tarjan, « Efficiency of a good but not linear set union algorithm. », J. ACM, vol. 22, no 2,‎ , p. 215–225
  4. (en) Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest et Clifford Stein, Introduction to Algorithms, MIT Press et McGraw-Hill, , 2e Ă©d. [dĂ©tail de l’édition]. « Chapter 21: Data structures for Disjoint Sets », pp. 498–524.
  5. Sanjoy Dasgupta, Christos H. Papadimitriou et Umesh Vazirani, Algorithms, McGraw-Hill, Inc., (ISBN 0073523402 et 9780073523408, lire en ligne)
  6. « Code source pour produire les images. »
  7. Sylvain Conchon, Jean-Christophe FilliĂątre: A persistent union-find data structure. ML 2007: 37-46
  8. Kevin Knight, « Unification: A Multidisciplinary Survey », ACM Comput. Surv., vol. 21,‎ , p. 93–124 (ISSN 0360-0300, DOI 10.1145/62029.62030, lire en ligne, consultĂ© le )

Sources

Cet article est issu de wikipedia. Text licence: CC BY-SA 4.0, Des conditions supplĂ©mentaires peuvent s’appliquer aux fichiers multimĂ©dias.