diff --git a/Images/uf_compression.png b/Images/uf_compression.png new file mode 100644 index 0000000000000000000000000000000000000000..58ee9b276621e92c9348804ac8e00aae614a76e1 Binary files /dev/null and b/Images/uf_compression.png differ diff --git a/Images/uf_compression.tex b/Images/uf_compression.tex new file mode 100644 index 0000000000000000000000000000000000000000..63ebb929cc4a229647f40327bfbf9ae20765090f --- /dev/null +++ b/Images/uf_compression.tex @@ -0,0 +1,56 @@ +\documentclass{standalone} +\usepackage{tikz} + +\begin{document} + +\tikzset{ + ufnode/.style={ + circle, + draw, + thick + }, + ufedge/.style={ + -latex, + thick + } +} + +\begin{tikzpicture} + \draw + (0,1) node[ufnode] (n00) {0} + (1,2) node[ufnode] (n10) {1} + (2,1) node[ufnode] (n20) {2} + (-1,0) node[ufnode] (n30) {3} + (1,0) node[ufnode] (n40) {4} + (1,-1) node[ufnode] (n50) {5} + ; + + \draw[ufedge] (n00) -- (n10) ; + \draw[ufedge] (n20) -- (n10) ; + \draw[ufedge] (n30) -- (n00) ; + \draw[ufedge] (n40) -- (n00) ; + \draw[ufedge] (n50) -- (n40) ; + \draw[ufedge] (n10) edge[loop above] (n10) ; + + \draw[ufedge] (4,1) -- (6.5,1) ; + + \draw + (-0.5,1) +(9,0) node[ufnode] (n01) {0} + (1,2) +(9,0) node[ufnode] (n11) {1} + (2.5,1) +(9,0) node[ufnode] (n21) {2} + (-1.5,0) +(9,0) node[ufnode] (n31) {3} + (0.5,1) +(9,0) node[ufnode] (n41) {4} + (1.5,1) +(9,0) node[ufnode] (n51) {5} + ; + + \draw[ufedge] (n01) -- (n11) ; + \draw[ufedge] (n21) -- (n11) ; + \draw[ufedge] (n31) -- (n01) ; + \draw[ufedge] (n41) -- (n11) ; + \draw[ufedge] (n51) -- (n11) ; + \draw[ufedge] (n11) edge[loop above] (n11) ; + + +\end{tikzpicture} + +\end{document} diff --git a/readme.md b/readme.md index 4d8f47ab4f2da7658978b52d9f21c87b70f71202..fd3e1051e391cd55be3378361c8ecb6f6850bfa5 100644 --- a/readme.md +++ b/readme.md @@ -173,7 +173,43 @@ peuvent être nettement améliorées grâce à deux idées simples à mettre en Lorsque vous cherchez la racine d'un arbre, vous partez d'un nœud et sautez de nœud en nœud pour la trouver. La compression de chemin fait en sorte qu'une fois -la racine trouvée, tous les nœuds sur le chemin changent de parent, pour prendre -directement la racine pour parente. +la racine trouvée, tous les nœuds sur le chemin changent de parent pour prendre +directement la racine pour parente. Cette optimisation est particulièrement +facile dans le cadre d'une implémentation récursive de la recherche de racine. +Dans l'image ci-dessous, une recherche de racine sur le noeud 5 va modifier les +parents de 5 et 4 pour qu'ils se placent directement sous 1. + + #### Minimisation des hauteurs + +La complexité d'une recherche dans le pire cas est la hauteur de l'arbre dans +laquelle elle est lancée. Lors de l'union de deux ensembles, il est possible +d'essayer de faire en sorte de n'augmenter les hauteurs des arbres que le moins +possible. Si les deux arbres dont de profondeurs différentes, en spécifiant la +racine de l'arbre le plus profond comme parente de la racine de l'arbre le moins +profond, la profondeur de l'arbre final est la même que celle de l'arbre le plus +profond. Ce n'est donc que lorsqu'on fusionne deux arbres de mêmes hauteurs +que nous obtenons un arbre de profondeur plus importante. + +La compression de chemin modifie la profondeur des arbres. Il est ainsi +difficile d'avoir la valeur exacte de leur profondeur. On se contente donc d'une +approximation, en faisant comme s'il n'y avait jamais eu de compression de +chemin. Cette approximation est donc toujours pire que la réalité. + +Pour stocker les hauteurs, il vous suffit d'ajouter un tableau qui stocke pour +chaque nœud une profondeur. Initialement toutes les profondeurs sont à 1. Lors +d'une fusion, seule les profondeurs des racines sont importantes. Les +profondeurs des nœuds ne restent donc utiles que tant qu'il sont la racine de +leur arbre. Dès qu'ils se retrouvent sous un parent, il n'est plus nécessaire de +mettre à jour leur profondeur. + +#### Complexité finale + +Avec ces deux optimisations, la compression de chemin devient beaucoup plus +efficace lorsque la complexité est examinée de façon amortie sur un grand nombre +d'opérations. Les deux opérations de recherche et d'union peuvent ainsi être +réalisées avec une complexité de l'ordre de l'inverse de la fonction +d'Ackermann. En pratique, pour des valeurs de n raisonnables, l'inverse de la +fonction d'Ackermann ne dépasse pas 5, donc cette complexité est très proche +d'une complexité constante.