• Aucun résultat trouvé

6.4 Parcours de graphes

6.4.2 Parcours en largeur

Une arborescence souvent associ´ee `a un graphe quelconque est l’arborescence des plus

courts chemins. C’est le graphe dans lequel ne sont conserv´ees que les arˆetes appartenant `

a un plus court chemin entre la racine et un autre sommet. On peut la construire grˆace `a un parcours en largeur d’abord du graphe (breadth-first traversal en anglais). Le principe est le suivant : on parcourt le graphe `a partir du sommet choisi comme racine en visitant tous les sommets situ´es `a distance (i.e profondeur) k de ce sommet, avant tous les sommets situ´es `a distance k + 1.

efinition 6.3. Dans un graphe G = (S, A), pour chaque sommet x, une arborescence des

plus courts chemins de racine x est une arborescence (Y, B, x) telle que

– un sommet y appartient `a Y si, et seulement si, il existe un chemin d’origine x et d’extr´emit´e y.

– la longueur du plus court chemin de x `a y dans G est ´egale `a la profondeur de y dans l’arborescence (Y, B, x).

Remarque : cette arborescence existe bien puisque, si (a1, a2, . . . , ap) est un plus court

chemin entre org(a1) et ext(ap), alors le chemin (a1, a2, . . . , ai) est un plus court chemin

entre org(a1) et ext(ai), pour tout i, 1≤ i ≤ p. En revanche, elle n’est pas toujours unique.

Th´eor`eme 6.4.1. Pour tout graphe G = (S, A), et tout sommet x de G, il existe une

arborescence des plus courts chemins de racine x.

Preuve. Soit x, un sommet de S. Nous allons construire une arborescence des plus courts

chemins de racine x. On consid`ere la suite {Yi}i d’ensembles de sommets suivante :

– Y0 ={x}.

– Y1 = Succ(x), l’ensemble des successeurs2 de x.

§ 6.4 Parcours de graphes ENSTA – cours IN101 – Pour i≥ 1, Yi+1est l’ensemble obtenu en consid´erant tous les successeurs des sommets

de Yi qui n’appartiennent pas `a

i j=1Yj.

Pour chaque Yi, i > 0, on d´efinit de plus l’ensemble Bi des arcs dont l’extr´emit´e est dans Yi et l’origine dans Yi−1. Attention Bi ne contient pas tous les arcs possibles, mais un seul

arc par ´el´ement de Yi : on veut que chaque ´el´ement n’ait qu’un seul p`ere. On pose ensuite Y =iYi, B =

iBi. Alors le graphe (Y, B) est par construction une arborescence. C’est

l’arborescence des plus courts chemins de racine x, d’apr`es la remarque ci-dessus.

Cette preuve donne un algorithme de construction de l’arborescence des plus courts chemins d’un sommet donn´e : comme pour le parcours en largeur d’un arbre on utilise une file qui g`ere les ensembles Yi, i.e. les sommets `a traiter (ce sont les sommets qui, `a un

moment donn´e de l’algorithme, ont ´et´e identifi´es comme successeurs, mais qui n’ont pas encore ´et´e parcourus ; autrement dit, ce sont les sommetsen attente). Par rapport `a un

parcours d’arbre, la pr´esence ´eventuelle de cycles fait que l’on utilise en plus un coloriage des sommets avec trois couleurs : les sommets en blanc sont ceux qui n’ont pas encore ´et´e trait´es (au d´epart, tous les sommets, sauf le sommet x choisi comme racine, sont blancs). Les sommets en gris sont ceux de la file, i.e. en attente de traitement, et les sommets en noir sont ceux d´ej`a trait´es (ils ont donc, `a un moment, ´et´e d´efil´es). On d´efinit aussi un tableau d qui indique la distance du sommet consid´er´e `a x ; en d’autres termes, d[v] est la profondeur du sommet v dans l’arborescence en cours de construction (on initialise chaque composante de d `a ∞).

Selon la structure de donn´ee utilis´ee pour repr´esenter le graphe, la fa¸con de programmer cet algorithme peut varier et la complexit´e de l’algorithme aussi ! On se place ici dans le cas le plus favorable : on a une structure de donn´ee similaire `a un arbre, o`u chaque sommet du graphe contient un pointeur vers la liste de ses successeurs, et en plus, les sommets du graphe sont tous index´es par des entiers compris entre 0 et n.

1 struct vertex { 2 int num; 3 vertices_list* successors; 4 }; 5 struct vertices_list { 6 vertex* vert; 7 vertices_list* next; 8 }; 9 int n;

10 int* color = NULL; 11 int* dist = NULL; 12 int* father = NULL; 13

14 void init(vertex* root, int nb) {

15 int i;

16 n = nb;

17 if (color == NULL) {

18 /* initialiser uniquement si cela n’a jamais ´et´e initialis´e */

20 dist = (int* ) malloc(n*sizeof(int ));

21 father = (int* ) malloc(n*sizeof(int ));

22 for (i=0; i<n; i++) {

23 color[i] = 0; /* 0 = blanc, 1 = gris, 2 = noir */

24 dist[i] = -1; /* -1 = infini */

25 father[i] = -1; /* -1 = pas de p`ere */

26 } 27 } 28 color[root->num] = 1; 29 dist[root->num] = 0; 30 } 31

32 void minimum_spanning_tree(vertex* root, int num) { 33 vertex* cur; 34 vertices_list* tmp; 35 init(root,num); 36 push(root); 37 while (!queue_is_empty()) { 38 while((cur=pop()) != NULL) { 39 tmp = cur->successors; 40 while (tmp != NULL) { 41 if (color[tmp->vert->num] == 0) {

42 /* si le noeud n’avait jamais ´et´e atteint, on fixe son

43 p`ere et on le met dans la file de noeuds `a traiter. */

44 father[tmp->vert->num] = cur->num; 45 dist[tmp->vert->num] = dist[cur->num]+1; 46 color[tmp->vert->num] = 1; 47 push(tmp->vert); 48 } 49 tmp = tmp->next; 50 } 51 color[cur->num] = 2; 52 } 53 swap_queues(); 54 } 55 }

Pour simplifier, la gestion des files `a ´et´e r´eduite `a son minimum : on a en fait deux files, l’une dans laquelle on ne fait que retirer des ´el´ements avec le fonction pop et l’autre dans laquelle on ajoute des ´el´ements avec la fonction push. La fonction swap queues permet d’´echanger ces deux files et la fonction queue is empty retourne vrai quand la premi`ere file est vide (on n’a plus de sommets `a retirer).

Chaque parcours en largeur `a partir d’un sommet fournissant une seule composante connexe du graphe, si le graphe en poss`ede plusieurs il faudra n´ecessairement appeler la fonction minimum spanning tree plusieurs fois avec comme argument `a chaque fois un sommet x non encore visit´e. Si on se donne un tableau vertices contenant des poin- teurs vers tous les sommets du graphe, un algorithme de parcours en largeur de toutes les composantes connexes est alors :

§ 6.4 Parcours de graphes ENSTA – cours IN101 1 6 2 8 3 4 7 5 0 0 1 1 2 3 1 1 2 0 father dist -1 4 7 0 3 -1 0 0 5 0 3 2 1 2 0 1 1 1 0 1 2 3 4 5 6 7 8

Figure 6.5 – Application de all spanning trees `a un graphe. Les sommets 0 et 5

sont des racines. Les arcs en pointill´es ne font pas partie d’une arborescence.

1 void all_spanning_trees(vertex** vertices, int nb) { 2 int i;

3 /* un premier parcours en partant du noeud 0 */

4 minimum_spanning_tree(vertices[0], nb);

5 for (i=1; i<nb; i++) {

6 if (color[i] != 2) {

7 /* si le noeud i n’a jamais ´et´e colori´e en noir */

8 minimum_spanning_tree(vertices[i], nb);

9 }

10 }

11 }

Complexit´e. Dans l’algorithme minimum spanning tree, la phase d’initialisation prend un temps en Θ(n) la premi`ere fois (on initialise des tableaux de taille n) et un temps constant les suivantes, puis, pour chaque sommet dans la file, on visite tous ses succes- seurs une seule fois. Chaque sommet ´etant visit´e (trait´e) une seule fois, la complexit´e de l’algorithme est en Θ(n +|A|).

Maintenant, si le graphe poss`ede plusieurs composantes connexes, on peut le d´ecom- poser en sous-graphes Gi = (Si, Ai). La complexit´e de l’algorithme all spanning trees

sera alors n pour la premi`ere initialisation, plus n pour la boucle sur les sommets, plus Θ(|Si|+|Ai|) pour chaque sous-graphe Gi. Au total on obtient une complexit´e en Θ(n+|A|).

Attention

Cette complexit´e est valable pour une repr´esentation du graphe par listes de succes- seurs, mais dans le cas d’une repr´esentation par matrice d’adjacence, l’acc`es `a tous les successeurs d’un sommet n´ecessite de parcourir une ligne compl`ete de la matrice. La complexit´e de l’algorithme devient alors Θ(n2).

Documents relatifs