Arbre couvrant minimal (Minimum spanning tree) CSI2510 Structures de données et algorithmes Définitions L'algorithme de Prim-Jarnik L'algorithme de Kruskal Arbres couvrants minimaux 1 Arbre couvrant minimal ACM versus PCC Sous-graphe couvrant Sous-graphe d’un graphe G contenant tous les sommets de G 10 1 ORD 10 1 Arbre couvrant 6 9 3 Arbre couvrant d’un graphe pondéré avec la somme minimale de poids des arêtes constituant l'arbre 8 DFW Réseaux informatiques Réseaux routiers 5 2 8 DFW DCA STL 4 STL 4 7 3 DCA 5 6 3 DFW DCA STL 4 8 ATL 7 9 2 5 2 ATL ATL Arbre des plus courts chemin Arbre couvrant minimal CSI2510 - ACM PIT DEN 6 9 7 10 1 PIT DEN PIT DEN Arbre couvrant minimal (ACM) Applications ORD ORD Sous-graphe couvrant qui est un arbre 2 CSI2510 - ACM 3 CSI2510 - ACM 4 4 1 Propriété des cycles Propriété des cycles Soit T un arbre couvrant minimal d’un graphe pondéré G Soit e une arête de G n’appartenant pas à T et soit C le cycle obtenu lorsqu’on ajoute e à T Pour chaque arête f dans C, poids(f) ≤ poids(e) Preuve: Par contradiction Si poids(f) > poids(e) nous pouvons obtenir un arbre couvrant de plus petit poids en remplaçant e par f 8 f 4 C 2 ORD 9 6 3 e 8 7 7 Remplacer f avec e produit un meilleur arbre couvrant PIT DEN 6 7 9 3 DCA STL 4 8 4 C 10 1 8 f 5 2 9 6 2 Dans tout cycle du graphe, les arêtes faisant partie d’un ACM ont des poids inferieurs aux autres arêtes (en pointillé) DFW 3 e 8 ATL 7 7 5 CSI2510 - ACM Propriété de partition U Preuve: Soit T un arbre couvrant minimal de G Si T ne contient pas e, soit C le cycle formé par l’addition de e à T et soit f une arête de C entre U et V Par la propriété de cycles, poids(f) ≤ poids(e) Alors, poids(f) = poids(e) Nous obtenons un autre ACM en remplaçant f par e CSI2510 - ACM 6 L’algorithme de Prim-Jarnik Propriété de partition: Considérons une partition des sommets de G en deux ensembles U et V. Soit e une arête de poids minimal entre U et V. Alors, il existe un arbre couvrant minimal de G contenant e CSI2510 - ACM f V 7 4 9 5 2 8 8 3 e 7 Remplacer f avec e donne un autre ACM U f 2 V 7 L'algorithme de Prim-Jarnik pour calculer un ACM est similaire à l'algorithme de Dijkstra Nous supposons que le graphe est connexe Nous choisissons un sommet arbitraire s et nous faisons grossir l’ACM comme un nuage de sommets, commençant de s Nous emmagasinons avec chaque sommet v une étiquette d(v) représentant le plus petit poids d'une arête reliant v à un sommet du nuage (au lieu de la somme totale des poids sur un chemin du sommet de départ jusqu’à v dans Dijkstra) 4 9 5 8 8 e 3 7 7 CSI2510 - ACM 8 2 L’algorithme de Prim-Jarnik L’algorithme de Prim-Jarnik Pour chaque étape Utiliser une file à priorité Q dont les clés sont les étiquettes D, et dont les éléments sont des paires sommet-arête Nous ajoutons au nuage le sommet extérieur u ayant la plus petite étiquette de distance Nous mettons à jour les étiquettes des sommets adjacents àu Clé : distance Élément: (sommet, arête de poids minimal) Tout sommet v peut être le sommet de départ (D[v] =0) On initialise toutes les valeurs de D[u] à “infini”, mais nous initialisons aussi E[v] (l’arête associé à v) à “null” Retourne l’arbre recouvrant minimal T. Nous pouvons réutiliser le code produit par Dijkstra, et ne changer que quelques parties. Observons le pseudo-code.... CSI2510 - ACM 9 Algorithm PrimJarnik(G): Entrée: Un graphe pondéré G. Sortie: Un ACM T pour G. Choisir un sommer v de G {bâtir l’arbre à partir de v} T ← {∅} D[v] ← 0 E[v] ← null Pour chaque sommet u ≠ v faire D[u] ← ∞ Soit Q une file à priorité contenant des sommets avec les étiquettes D comme clé Tant que Q n’est pas vide (u,E(u)) ← Q.removeMinElement() ajouter le sommet u et l’arête E[u] à T Pour chaque sommet z adjacent à u et dans Q {effectuer la relaxation de l’arête (u, z) } if poids(u, z) < D[z] then D[z] ←weight(u, z) E[z] ← (u, z) mettre à jour D[z] retourner l’arbre T CSI2510 - ACM 11 CSI2510 - ACM 10 Prim-Jarnik: Exemple 2 D 7 4 9 8 C 5 F 8 A 2 0 9 5 C A 2 ∞ F 8 2 3 E 7 8 8 4 5 ∞ F 3 E 7 7 B 8 A D 7 4 9 5 C 5 2 7 7 2 B 3 E 7 D 7 ∞ 8 0 2 B 2 0 ∞ 0 CSI2510 - ACM A 7 B 4 9 5 C 5 F 8 8 7 D 7 7 3 E 7 4 7 12 3 Prim-Jarnik: Exemple 0 ACM versus PCC A 8 4 2 B 2 2 0 7 B 9 5 C 5 F 8 C 3 Djkstra (Shortest Path Spanning Tree) 3 2 2 D 7 9 5 C A 2 F 8 8 0 A 8 4 5 7 4 8 B 7 2 E 3 5 CSI2510 - ACM C 3 3 E 1 D 9 ∞ F 4 5 PRIM (Minimum Spanning Tree) 0 7 B 2 E 3 E 7 4 7 ∞ 2 4 8 A D 7 8 13 Prim-Jarnik ∞ 0 4 2 1 A 8 D 4 3 B 7 7 8 9 5 F ∞ 2 11 4 2 C 3 E ∞ 3 CSI2510 - ACM 2 1 D F 5 1 4 9 ∞ 9 14 Prim-Jarnik: Analyse de complexité Opérations sur les graphes: Méthode incidentEdges est appelée une fois pour chaque sommet Opérations d’étiquetage: Nous plaçons/obtenons les étiquettes du sommet z O(deg(z)) fois placer/obtenir les étiquettes prend O(1) Opérations de file de priorité (implémenté par un tas): Chaque sommet est inséré une fois et enlevé une fois de la file à priorité, où chaque insertion ou suppression prend O(log n) La clé d'un sommet w dans la file à priorité est modifiée au plus deg(w) fois, où chaque changement de clé prend O(log n) L’algorithme applique la propriété des cycles Soit (u,v) l’arête minimale pour une itération donnée. S’il y a un ACM ne contenant pas (u,v), alors (u,v) complète un cycle. Puisque (u,v) a été préférée à toute autres arête connectant v au groupe, elle doit avoir un poids inférieur ou égal au poids de l’autre arête. Un nouveau ACM peut être formé par permutation CSI2510 - ACM 15 CSI2510 - ACM 16 4 Dijkstra vs. Prim-Jarnik Prim-Jarnik: Analyse de complexité Algorithm DijkstraShortestPaths(G, s) Q ← new heap-based priority queue L’algorithme de Prim-Jarnik est exécuté en O((n + m) log n) si le graphe est représenté par la structure de liste d’adjacence (et en utilisant un tas) for all v ∈ G.vertices() if v = s setDistance(v, 0) else setDistance(v, ∞) setParent(v, ∅) l ← Q.insert(getDistance(v), v) setLocator(v,l) while !Q.isEmpty() u ← Q.removeMin() for all e ∈ G.incidentEdges(u) z ← G.opposite(u,e) r ← getDistance(u) + weight(e) if r < getDistance(z) setDistance(z,r) setParent(z,e) Q.replaceKey(getLocator(z),r) Rappelez-vous que Sv deg(v) = 2m Le temps d’exécution est O(m log n) parce que le graphe est connexe CSI2510 - ACM 17 L’algorithme de Kruskal CSI2510 - ACM 18 L’algorithme de Kruskal Chaque sommet est emmagasiné initialement comme son propre groupe. A chaque itération, l’arête de poids minimal dans le graphe est ajoutée à l'arbre couvrant si elle relie 2 groupes distincts. L'algorithme se termine quand tous les sommets sont dans le même groupe. Une file à priorité emmagasine les arêtes extérieures (hors du nuage) clé: poids élément: arête À la fin de l’algorithme On se retrouve avec un nuage qui entoure l’ACM Un arbre T qui est notre ACM Ceci est une application de la propriété de partition! Si l’arête minimale pour une itération donnée est (u,v), alors si nous considérons une partition de G avec u dans un groupe et v dans l’autre, alors la propriété de partition dit qu’il doit y avoir un ACM contenant (u,v) CSI2510 - ACM Algorithm PrimJarnikMST(G) Q ← new heap-based priority queue s ← a vertex of G for all v ∈ G.vertices() if v = s setDistance(v, 0) else setDistance(v, ∞) setParent(v, ∅) l ← Q.insert(getDistance(v), v) setLocator(v,l) while !Q.isEmpty() u ← Q.removeMin() for all e ∈ G.incidentEdges(u) z ← G.opposite(u,e) r ← weight(e) if r < getDistance(z) setDistance(z,r) setParent(z,e) Q.replaceKey(getLocator(z),r) 19 Algorithm KruskalMST(G) Pour chaque sommet V dans G faire définir Cloud(v) de {v} Soit Q une file à priorité. Insérer toutes les arête dans Q en utilisant leur poids comme clé T∅ Tant que T a moins que n-1 sommets Faire sommet e = Q.removeMin() Soit u, v les extrémités de e Si Cloud(v) ≠ Cloud(u) alors ajouter arête e à T Fusionner Cloud(v) et Cloud(u) return T CSI2510 - ACM 20 5 Structure de données pour l’algorithme de Kruskal Représentation d’une partition: L’algorithme maintient une forêt d’arbres Une arête est acceptée, si elle relie deux arbres distincts Nous avons besoin d’une structure de données qui maintient une partition c.-à-d. une collection d’ensembles disjoints, avec les opérations: Chaque élément d’un ensemble est mis en mémoire dans une séquence (l’ensemble pointe vers la séquence contenant ces éléments) Chaque élément à un pointeur vers l’ensemble L’opération find(u) se fait en O(1) et retourne l’ensemble dont u fait partie Pour l’opération union(u,v), nous déplaçons les éléments du plus petit ensemble dans la séquence du plus grand ensemble et nous mettons à jour leur pointeur La complexité en temps de union(u,v) est min(nu,nv), où nu et nv sont les tailles des ensembles contenant u et v find(u): retourne l’ensemble contenant u union(u,v): remplace les ensembles contenant u et v par leur union Chaque fois qu’un élément est traité, il est mis dans un ensemble de taille au moins double, donc chaque élément est traité au plus log n fois CSI2510 - ACM 21 Implémentation à base de partition CSI2510 - ACM Exemple Une version à base de partition de l’algorithme de Kruskal exécute les fusions des nuages comme des union(u,v) et les tests comme des find(u) 23 9 5 1 G 8 B Algorithm Kruskal(G): Entrée: Un graphe pondéré G. Sortie: Un ACM T pour G. Soit P une partition des sommets de G, où chaque sommet est dans un ensemble séparé. Soit Q une file à priorité gardant en mémoire les arêtes de G, ordonnées selon leur poids Soit T un arbre initialement vide Tant que T contient moins que n-1 arêtes faire (u,v) ← Q.removeMinElement() Si P.find(u) != P.find(v) alors Temps: O((n+m)log n) Ajouter (u,v) à T P.union(u,v) retourner T CSI2510 - ACM 22 7 F H D 9 C 11 7 C 11 7 A E 6 F 3 D H 1 2 CSI2510 - ACM F 3 H D 2 G 9 5 C 11 7 A 10 6 8 B 4 4 E 10 G 8 5 9 5 1 2 10 B A 6 3 C 11 A 1 E G 8 B 4 E 4 6 F 3 D H 2 10 24 6 Exemple (suite) 9 5 1 G 8 B E 7 F 3 C 11 A 6 H D 9 5 1 G 8 B 4 C 11 2 7 A 10 4 E 6 F 3 H D 2 10 4 étapes 1 9 5 C 11 7 A G 8 B E 6 F 3 D H 1 2 9 5 C 11 7 A 10 CSI2510 - ACM G 8 B 4 E 4 6 F 3 D H 2 10 25 7