Arbre couvrant minimal (Minimal spanning tree) 2704 BOS 867 849 PVD ORD 337 1391 1464 1235 144 JFK 1258 184 802 SFO LAX 187 740 621 1846 BWI 1090 DFW 946 1121 MIA 2342 1 Arbre couvrant minimal Définitions L'algorithme de Prim-Jarnik L'algorithme de Kruskal 2 1 Arbre couvrant minimal Sous-graphe couvrant Arbre couvrant Arbre couvrant d’un graphe pondéré avec la somme des poids des arêtes qui constituent l'arbre est minimal 10 1 DEN Sous-graphe couvrant qui est un arbre Arbre couvrant minimal (ACM) ORD Sous-graphe d’un graphe G contenant tous les sommets de G PIT 6 9 7 3 STL 4 DCA 5 8 DFW 2 ATL Applications Réseaux informatiques Réseaux routiers 3 Propriété de cycles Propriété de 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 f 2 8 4 C 6 9 3 e 8 7 7 Remplaçant f avec e produit un mieux arbre couvrant f 2 6 8 4 C 9 3 8 e 7 7 4 2 Propriété de cycles Dans tout cycle du graphe, les arêtes faisant partie d’un ACM ont des poids inferieurs aux autres arêtes (en pointillé) ORD 10 1 PIT DEN 6 9 7 3 DCA STL 4 5 8 DFW 2 ATL 5 Propriété de partition 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 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 U f V 7 4 9 5 2 8 8 3 e 7 Remplacer f avec e donne un autre ACM U 2 f V 7 4 9 5 8 8 3 e 7 6 3 L’algorithme de Prim-Jarnik 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 connectant v à n'importe quel sommet dans le nuage (comme opposé à la somme totale des poids sur un chemin du sommet de départ jusqu’à u). 7 L’algorithme de Prim-Jarnik Pour chaque étape 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 8 4 Utiliser une file à priorité Q dont les clés sont les étiquettes D, et dont les éléments sont des paires sommet-arête. Clé : distance Élément: sommet Tout sommet v peut être le sommet de départ. On continue à initialiser toutes les valeurs de D[u] à “infini”, mais nous initialisons aussi E[u] (l’arête associé à u) à “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.... 9 Algorithm PrimJarnik(G): Entrée: Un graphe pondéré G. Sortie: Un ACM T pour G. pick any vertex v of G {grow the tree starting with vertex v} T ← {v} D[u] ← 0 E[u] ← ∅ for each vertex u ≠ v do D[u] ← ∞ let Q be a priority queue that contains vertices, using the D labels as keys while Q ≠ ∅ do {pull u into the cloud C} u ← Q.removeMinElement() add vertex u and edge E[u] to T for each vertex z adjacent to u do if z is in Q {perform the relaxation operation on edge (u, z) } if weight(u, z) < D[z] then D[z] ←weight(u, z) E[z] ← (u, z) change the key of z in Q to D[z] return tree T 10 5 Exemple 2 D 7 B 0 A B A A F 8 7 2 ∞ F E B 3 0 A F 8 8 7 4 9 5 C 5 2 7 7 D 7 E 7 ∞ 3 E 7 4 7 5 4 9 5 C 8 0 8 8 B 2 7 9 5 C 5 2 D 7 ∞ 7 D 7 3 E 7 2 0 F 8 8 2 4 9 8 C 5 2 ∞ 4 3 7 11 Exemple (suite) 2 2 B 5 A 7 4 9 5 C F 8 8 0 D 7 7 E 4 3 3 2 2 B 5 A 7 4 9 5 C F 8 8 0 D 7 7 E 4 3 3 12 6 Prim-Jarnik… Pourquoi il marche Ceci est une application de la propriété de Cycles! Soit (u,v) l’arête minimale dans quelques itérations. S’il y a un ACM ne contenant pas (u,v), alors (u,v) complète un cycle. Puisque (u,v) a été considéré avant quelque autre arête connectant v au groupe, il doit avoir un poids inférieur ou égal au poids de l’autre arête. Un nouveau ACM peut être formé par permutation 13 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 de sommet z O(deg(z)) fois placer/obtenir les étiquettes prend O(1) Opérations de file de priorité 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) 14 7 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 Rappelez-vous que S v deg(v) = 2m Le temps d’exécution est O(m log n) parce que le graphe est connexe 15 Dijkstra vs. Prim-Jarnik Algorithm DijkstraShortestPaths(G, s) Q ← new heap-based priority queue 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) 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) 16 8 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. Ceci est une application de la propriété de partition! Si l’arête minimale dans quelques itérations 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) 17 L’algorithme de Kruskal Une file de 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 Algorithm KruskalMST(G) for each vertex V in G do define a Cloud(v) of {v} let Q be a priority queue. Insert all edges into Q using their weights as the key T∅ while T has fewer than n-1 edges do edge e = T.removeMin() Let u, v be the endpoints of e if Cloud(v) ≠ Cloud(u) then Add edge e to T Merge Cloud(v) and Cloud(u) return T 18 9 Structure de données pour l’algorithme de Kruskal 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: -find(u): retourne l’ensemble contenant u -union(u,v): remplace les ensembles contenant u et v par leur union 19 Représentation d’une partition: 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 bougons 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 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 20 10 Implémentation à base de partition Une version à base de partition de l’algorithme de Kruskal exécute les fusions du nuage comme des union(u,v) et les tests comme des find(u). 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 while Q n’est pas vide do (u,v) ← Q.removeMinElement() if P.find(u) != P.find(v) then Ajouter (u,v) à T P.union(u,v) return T Temps: O((n+m)log n) 21 Exemple 5 1 6 C 11 10 9 5 1 2 C 11 7 A E 6 F 3 D H 1 2 A 6 5 H C 11 10 2 G 9 7 F 3 8 B 4 4 E D 10 G 9 7 H D 8 5 F 3 G 8 B 4 E C 11 10 B A 9 7 A 1 G 8 B 4 E 6 F 3 D H 2 22 11 Exemple (suite) 6 9 C 11 7 10 H F 3 D H 4 E 6 F 3 H D 10 2 4 etapes et ap es 6 C 11 7 A 4 E 9 5 1 2 G 8 5 F 3 D 10 B A E C 11 7 A 1 9 G 8 B 4 1 2 A G 8 B 2 5 1 G 8 B 5 9 C 11 7 10 4 E 6 F 3 D H 2 23 12