8.1 8.1.1 Arbre couvrant de poids minimum Le problème Le problème est le suivant : — entrée : un graphe connexe, non orienté, pondéré (pas forcément positivement) non orienté G = (S , A) ; — sortie : un arbre couvrant de poids minimal E ⊆ A, c’est à dire un ensemble d’arêtes E, X qui connecte tous les sommets, et tel que poids(a, b) est minimal. (a,b)∈E Remarquez que si le graphe initial est pondéré positivement, un sous-graphe couvrant de poids minimal est forcément... un arbre couvrant. Applications : • Circuits électroniques • Réseaux Exemple 35. Dessinez un graphe. Le graphe représente une ville dans laquelle il n’y a malheureusement pas encore de pistes cyclables. Les nœuds sont des maisons et les arcs représentent les routes. Le poids d’un arc est la longueur de la route. Le maire souhaite placer des pistes cyclables entre les maisons de façon à ce qu’il est possible d’aller d’une maison à une autre en prenant une piste. L’arbre couvrant donne une solution la moins chère. A 1 1 3 B 1 1 1 D 1 H 8.1.2 C E 1 4 F 1 G 25 I Algorithme de Kruskal L’algorithme date de 1956. Kruskal (1928-2010) a aussi travaillé dans d’autres domaines : statistique et linguistique notamment. Mais il reste vraiment célèbre pour son algorithme de calcul d’un arbre couvrant minimal, qui est devenu un classique des cours d’algorithmique. Principe Le principe est on ne peut plus glouton : on débute avec E = ∅ et tant que E n’est pas un arbre, on ajoute à E l’arête la plus légère parmi les arêtes non traités si elle ne crée pas de cycles dans E. Cela ne vous rappelle rien ? L’algorithme de Kruskal est une généralisation de l’algorithme de génération de labyrinthe vu en section 5.1. Au début, comme E = ∅, le sous-graphe en construction n’a pas d’arcs et a donc autant de composantes connexes que de sommets. Puis lorsque l’on traite une arête {x, y}, on regarde si x est y ne sont pas déjà relié, ie. si x et y ne sont pas dans la même composante connexe. Pour cela, on va utiliser une structure de données abstraite pour représenter une partition d’un ensemble fini. Il s’agit de union-find vu au chapitre 5 et ses opérations sont : François Schwarzentruber Page 98 ALGO1 - ENS Rennes • créer_union_find : crée la partition [ {{s}} : s∈S • find : Trouve la composante de x • union : fusionne les composantes de x et y. Algorithme Le pseudo-code de l’algorithme de Kruskal est l’algorithme 28. Algorithme 28 : Algorithme de Kruskal Entrées : Un graphe G = (S , A) pondéré non orienté Sorties : Un ACM (arbre couvrant minimal) de G CC := créer_union_find(S ) E := ∅ L := tri(A) par ordre croissant des poids. pour {x, y} ∈ L (dans l’ordre) faire si CC. f ind(x) , CC. f ind(y) alors E := E ∪ {{x, y}} CC.union(x, y) retourner E Théorème. À la fin, E contient les arêtes d’un ACM. Démonstration. On va démontrer l’invariant suivant I : 1. il existe T ACM tel que E ⊆ T 2. et pour tout x, y ∈ S x et y sont reliés pas des arêtes dans E ssi x et y sont dans la même composante dans CC. — Initialisation : E = ∅, et il existe un ACM (qui trivialement contient les arêtes de E). Par ailleurs : x et y sont reliés par des arêtes de E ssi (car E est vide) x=y ssi (car CC est la partition composée de singletons) x et y sont dans la même classe dans CC — Conservation : Supposons que l’invariant I est vraie avant un tour de boucle en (*). Montrons qu’il reste vraie après le tour de boucle en (∆). (∗) if CC. f ind(x) , CC. f ind(y) then E := E ∪ {{x, y}} CC.union(x, y) endIf (∆) 1. Occupons nous du point 1. On a ajouté une arête {x, y} à E. On a il existe T ACM tel que E \ {x, y} ⊆ T et on veut montrer que que le point 1 de l’invariant est conservée, c’est à dire qu’il existe bien un T 0 ACM tel que E ⊆ T 0 . François Schwarzentruber Page 99 ALGO1 - ENS Rennes — Si (par chance !) {x, y} ∈ T , alors la première partie de l’invariant est conservée en prenant T 0 = T . — Sinon, on construit un ACM T 0 qui contient E. La construction de T 0 s’appuie sur la donnée de T . Voici la situation actuelle : — en noir fin : T — en vert gros : E y x a rête té jou ar On rappelle la caractérisation des arbres : arbre ⇔ connexe et |S | − 1 arcs Comme T est un arbre, il est connexe et x et y sont reliés par un chemin c dans T . Par le point 2 de I en (*), comme CC. f ind(x) , CC. f ind(y), x et y ne peuvent pas être reliés par uniquement des arêtes de E \ {x, y}. Donc une des arêtes du chemin c n’est pas dans E. Soit {u, v} une telle arête de T \ E (reporté en trait bleu sur le dessin d’avant). On construit T 0 à partir de T en supprimant cette arête bleu et en ajoutant {x, y}. Formellement, on définit T 0 par T 0 = T ∪ {{x, y}} \ {{u, v}}. Ainsi E ⊆ T 0 . Voici T 0 dessiné en trait noir fin : y x Montrons que T 0 est un ACM. En effet, — T 0 est connexe, T 0 et T ont même nombre d’arêtes (|S | − 1) donc c’est un arbre. — Comme {u, v} n’a pas encore été choisie mais que {x, y} a été choisi et que les arêtes sont triées, on a poids({x, y}) ≤ poids({u, v}). Ainsi : poids(T 0 ) = poids(T ) + poids({x, y}) − poids({u, v}) 6 poids(T ). Donc le poids de T 0 est minimal. — Montrons le point 2 de l’invariant. Soit {x, y} l’arête ajouté à E. Montrons que x0 et y0 sont reliés pas des arêtes dans E ssi x0 et y0 sont dans la même composante dans CC. On s’intéresse à deux cas clefs. Cas où x0 était la classe de x ety0 dans la classe y en (∗) François Schwarzentruber Page 100 ALGO1 - ENS Rennes D’une part, par l’invariant en (∗), il existe un chemin c x dans E \ {{x, y}} de x0 à x et un chemin de cy dans E \ {{x, y}} de y à y0 . On construit alors un chemin de E en concaténant c x , x ↔ y et cy .On a x0 et y0 sont reliés pas des arêtes dans E. D’autre part, les classes de x et y sont fusionnées par CC.union(x, y). Donc x0 et y0 sont dans la même composante dans CC. Cas où x0 est dans la classe x et y0 dans une autre classe que x ou y en (∗) D’une part, montrons qu’il n’y a pas de chemin dans E de x0 à y0 . Par l’absurde, supposons qu’il existe un tel chemin. Si ce chemin est intégralement dans E \ {{x, y}}, alors par l’invariant, x0 et y0 devraient être dans la même classe en (∗) ce qui n’est pas le cas. Donc le chemin dans E de x0 à y0 passe par l’arête {x, y}. Mais alors soit y0 est dans la classe de x ou y... Non. D’autre part, les classes de x0 et y0 ne sont pas fusionnées par CC. À présent, montrons que E est un ACM. — D’une part, (S , E) est un arbre. — D’une part (S , E) est connexe. Cela resulte de la connexité de G et du traitement systématique de toute les arêtes de G. En effet, si x, y ∈ S , on sait que x et y sont reliés dans G : x = x1 ↔ x2 ↔ · · · ↔ xn ↔ y. On construit alors un chemin de x à y avec des arêtes de E de la manière suivante. Pour tout i, si {xi , xi+1 } ∈ E, on prend cette arête dans le chemin. Sinon, si {xi , xi+1 } < E, lorsque l’algorithme a traité {xi , xi+1 }, {xi , xi+1 } n’a pas été ajouté à E. On avait xi et xi+1 dans la même composante CC. Donc, l’invariant nous dit qu’il y avait un chemin de xi à xi+1 dans E (il est toujours intégralement composé d’arêtes de E puisque E est croissant lors de l’algorithme). Donc on prend ce chemin pour aller de xi à xi+1 . On a ainsi construit un chemin de x à y avec des arêtes de E. — D’autre part, il est acyclique. S’il contenait un cycle, regardons la dernière arête {x, y} ajouté de ce cycle. Par l’invariant, il existait un chemin de x à y donc l’arête n’aurait pas dû être ajoutée. Contradiction. — D’autre part, il est minimal. En effet, par l’invariant, il existe T 0 ACM tel que E ⊆ T 0 . (S , E) est un arbre donc il contient |S | − 1 arêtes. De même pour T 0 . Donc E = T 0 . Complexité Théorème. L’algorithme de Kruskal s’exécute en |creer_union_ f ind| + O(A ln(A)) + O(A(| f ind| + |union|)). Démonstration. Il y a la création de la structure union find, puis le tri, puis une boucle, dont chaque passage de boucle appelle f ind et union. Autrement dit pour des arbres de type union-find : — |creer_union_ f ind| : O(S ) ; — f ind et union en O(ln S ). Bilan : Comme |S | − 1 ≤ |A|, on a dans le pire des cas une complexité en O(A ln(A)). Cela peut se réécrire en O(A ln(S )) car |A| ≤ |S |2 et donc ln A = 2 ln S . Si le tri de A a déjà été fait alors la complexité peut être en O(A ln∗ (S )) avec la structure union find avec compression de chemin. François Schwarzentruber Page 101 ALGO1 - ENS Rennes 8.1.3 Algorithme de Prim L’algorithme date de 1957... Kruskal a aussi participé à son élaboration. Attention ! Il ressemble comme deux gouttes d’eau à l’algorithme Dijkstra ! Ce n’est pas sans raison : Dijkstra s’en inspiré en 1959. On utilise une stratégie gloutonne : on ajoute à E (arbre qui grossit pendant l’algorithme) un arc u ∈ E → t < E de poids minimum. Algorithme 29 : Algorithme de Prim Entrées : Un graphe G = (S , A) Sorties : Un ACM de G pour s ∈ S faire cout[s] :=+∞ pred[s] :=∅ u0 := un sommet de G cout[u0 ] :=0 F := filedepriorité (S ,cout) tant que F , ∅ faire t := F.défilemin pour tout u tel que t → u ∈ A faire si cout[u]> poids(t → u) alors cout[u] :=poids(t → u) F.notifierDiminution(u) pred[u] :=t retourner pred Au niveau de la complexité, cela se passe comme pour l’algorithme de Djikstra : Théorème. L’algorithme effectue O(|S | + |S ||défiler_min| + |file_créer| + |A||MàJ|). Remarque. Avec une implémentation sous forme de tableau, on a une complexité en O(|S |2 ). Sous forme d’un tas, c’est O((|S | + |A|) log(|S |)). Sous forme d’un tas de Fibonacci, c’est O(|S | log(|S |) + |A|). 8.1.4 Kruskal VS Prim On note les complexités (avec les structures de données qui donnent les meilleures complexités) et les avantages des deux algorithmes. Kruskal O(A ln S ) graphe creux rapide si déjà les arcs sont déjà triés 8.2 Prim O(S ln S + A) graphe dense on a toujours une solution partielle Formules de Horn [DPV06], p. 157 Dans cette section, on veut faire du raisonnement automatique. Alfred Horn (1918-2001) est un mathématicien, logicien américain décrit en 1951 les clauses de Horn comme fondement de la programmation logique. Les clauses de Horn sont un fragment logique qui permettent d’exprimer des règles qui sont sous la forme suivante : — Des implications comme a ∧ b ∧ c → d. Cette expression signifie ‘Si a, b, c sont vraies alors d est vraie’ ; François Schwarzentruber Page 102 ALGO1 - ENS Rennes