Algorithmique — L3 TD 7 : Parcours de Graphes

publicité
Algorithmique — L3
TD 7 : Parcours de Graphes
1
2
3
6
4
5
Fig. 1 – Un graphe orienté
Exercice 1 : Appliquer à ce graphe l’algorithme de parcours en largeur (le sommet origine est
indiqué par une simple flèche entrante). L’arbre de parcours en largeur résultant sera présenté par
un schéma dans lequel les sommets de profondeur égale seront mis à la même hauteur, le sommet
origine étant mis en haut.
Exercice 2 : Soit un graphe orienté G = (V, A) et soit s ∈ V . Soit une arborescence T =
(VT , AT ) telle que AT ⊆ A et VT est l’ensemble des sommets de G accessibles depuis s (y compris
s lui-même), et telle que pour tout sommet v ∈ VT , le chemin de s à v dans T soit un plus court
chemin de s à v dans G. Est-il toujours possible d’obtenir T par un certain parcours en largeur de
G depuis s ?
Exercice 3 : Donnez un algorithme calculant l’ensemble des composantes connexes d’un graphe
non orienté et calculez en la complexité.
Exercice 4 : Comment calculer l’ensemble des sommets accessibles à partir d’un sommet donné
dans un graphe orienté ? Donnez un algorithme permettant de calculer la composante fortement
connexe d’un sommet v. Quel est sa complexité ?
Exercice 5 : Appliquer au graphe de la figure 1 l’algorithme de parcours en profondeur. Le
résultat sera présenté sous la forme d’une arborescence en profondeur, en distinguant les arcs de
liaisons des autres types d’arcs.
Exercice 6 : Modifier l’algorithme de parcours en profondeur donné en cours de telle sorte
qu’il affiche pour chaque arc du graphe son type (arc de l’arbre de parcours, arc de retour, arc
transversal, arc en avant).
Exercice 7 :
1. Quel type de parcours est-il préférable d’utiliser (une seule fois) pour les problèmes suivants :
(a) trouver et afficher un circuit s’il en existe,
(b) trouver et afficher un circuit de longueur minimale contenant un sommet donné,
2. Le programme suivant se veut être une solution de a). Malheureusement, il ne trouve pas
toujours un circuit et parfois il trouve un circuit qui n’en est pas un. Expliquer pourquoi et
corriger l’algorithme pour résoudre le problème
Variables Globales:
booleen marquage[V] initialisé à faux
sommet père[V] initialisé à null
Variable locale sommet v
Prendre un sommet v
si( !cherche_circuit(v) )
afficher ‘‘pas de circuit’’
*****************************************
//retourne vrai si un circuit trouvé
boolean cherche_circuit(sommet v){
Variable locale sommet x,y
marquage[v] = vrai
pour chaque sommet x tel que (v,x) est un arc de G faire{
si( marquage[x] == faux) faire{
père[x] = v
si( cherche_circuit(x) ) \\ si trouvé alors terminer
retourner vrai
}
sinon{
\\afficher le circuit trouvé
afficher x
y = père[x]
tant que (y != x) faire{
afficher(y)
y = père[y]
}
returner vrai
}
} //fin pour
retourner faux
}
3. Écrivez une solution pour l’autre problème et montrer :
– qu’elle termine ;
– que, si on calcule un chemin, celui-ci correspond bien à ce que l’on cherche ;
– que, si le chemin recherché existe, l’algorithme le trouve.
4. Les trois points précédents sont-ils suffisants pour assurer que l’algorithme est correct ?
Exercice 8 : Donner un algorithme qui vérifie si un graphe non orienté possède un cycle.
Correction 1 : Un pseudo-code possible du parcours en largeur :
Procedure PL(sommet v)
Variables :
booleen marquage[V] initialise a faux
sommet x
File Afaire
Debut :
marquage[v]=vrai
enfiler (v,Afaire)
tant que (Afaire non vide) faire
v=defiler(Afaire)
pour chaque voisin x de v faire
si marquage[x]==faux faire
enfiler[x]
marquage[x]=vrai
Fin
À appliquer sur le graphe...
Correction 2 : : non ! Par exemple, le graphe de sommets {a, b, c, d, e} et d’arêtes {(ab), (ac),
(bd), (be), (cd), (ce)}. L’arbre d’arêtes {(ab), (ac), (bd), (ce)} ne peut être obtenu par parcours en
largeur, mais est bien un arbre de plus courts chemins partants de a.
Correction 3 : On fait un parcours avec relance. Pour chaque relance, une composante :
Variables globales:
booleen marquage[V] initialise a faux
sommet x,y
entier i
File Afaire
Debut :
Pour x de 1 a n
Si marquage[x] = faux
Afficher ‘‘Composante’’ i++ ‘‘:’’
PL(x)
Fin
Procedure PL(sommet v)
marquage[v]=vrai
enfiler (v,Afaire)
tant que (Afaire non vide) faire
v=defiler(Afaire)
Afficher v
pour chaque voisin y de v faire
si marquage[y]==faux faire
enfiler[y]
marquage[y]=vrai
Complexité : linéaire. C’est à dire O(n + m) sur les graphes codés en listes d’adjacence, et en
O(n2 ) s’ils sont codés en matrices d’adjacence.
Correction 4 : Il n’y a rien a changer si le graphe est orienté : on fait PL(v) avec l’algo
précédent pour avoir les sommets accessibles depuis v. La composante fortemement connexe de v
est formée des sommets accessibles et co-accessibles depuis v. Donc
– On calcule (par parcours en profondeur) les sommets accessibles depuis v : liste A
– On fait la transposée de G : chaque arc (ab) est replacé par (ba)
– On calcule les sommets accessibles depuis v dans le transposé : liste C (co-accessibles dans
G !)
– La composante fortement connexe est A ∩ C.
Les trois premières étapes se font en O(n + m) sur les graphes codés en listes d’adjacence, et
en O(n2 ) s’ils sont codés en matrices d’adjacence. La dernière est en O(n).
Correction 5 : On applique l’algo (cf exercice suivant) sur le graphe...
Correction 6 : Un pseudo-code possible du parcours en profondeur avec relance. On n’utilise
pas de marques : les dates remplacent. Un début à -1 signifie un sommet non encore atteint, un
début ≥ 0 mais une fin de -1 signifie un sommet en cours de visite.
Variables globales:
sommet x,y
entier temps = 0, debut[V], fin[V] tableaux initialises à -1
Debut :
Pour x de 1 a n
Si marquage[x] = faux
PP(x)
Fin
Procedure PP(sommet v)
debut[v] = temps++
pour chaque voisin y de v faire
si debut[y] == -1
Afficher (v,y) est un arc
PP(y)
sinon si fin[v] == -1
Afficher (v,y) est un arc
sinon si fin[y] < debut [v]
Afficher (v,y) est un arc
sinon
Afficher (v,y) est un arc
de parcours
de retour (aussi appelé en arrière)
transversal
en avant
fin[v] = temps++
Correction 7 :
1a) Un parcours en profondeur
1b) Un parcours en largeur
2 C’est un parcours en profondeur, donc a priori c’est une solution au a).
Le premier problème c’est que s’il y a plusieurs composantes connexes dans le graphe, on
risque de prendre un sommet dans une mauvaise composante et passer à côté d’un circuit.
Pour prendre ça en compte, il suffit de vérifier s’il reste des sommets non traités avant de
conclure qu’il n’y a pas de circuit.
Un problème plus sérieux est illustré en faisant tourner l’algo sur le graphe de la figure . Si
on a que deux marquages l’arc transversale peut être pris pour un arc de retour donnant un
faux circuit.
Solutions :
Une solution vient du théorème suivant :
Théorème 1 Dans un graphe orienté il existe un circuit si et seulement si dans chaque
parcours en profondeur il existe un arc de retour.
Donc la solution consiste à faire un parcours en profondeur et afficher un circuit et terminer
dès qu’on trouve un arc de retour. Si pas d’arc de retour alors pas de circuit. Cela donne
l’algorithme suivant :
Variables Globales:
booléen couleur[V] initialise à blanc
sommets père[V] initialise à null
Variables locales sommet v
booléen trouver = faux
pour tout sommet v faire{
si( couleur[v] == blanc && ( trouver = cherche_circuit(v) )
break //sortir de la boucle pour si trouvé
}
si( !trouver )
afficher ‘‘pas de circuit’’
*****************************************
booléen cherche_circuit(sommet v){
sommet x,y
couleur[v] = gris
pour chaque sommet x tel que (v,x) un arc de G faire{
si( couleur[x] == blanc){
père[x] = v
si( cherche_circuit(x) )
retourner vrai //circuit trouvé et affiché
//dans l’appel récursif
}
sinon si( couleur[x] == gris){
//afficher le circuit
y = v
tant que (y != x) faire{
afficher y
y = père[y]
}
afficher x
retourner vrai
}
} //fin pour
couleur[v] = noir
retourner faux
}
L’algorithme se termine pour les même raison que l’algorithme de parcours en profondeur
avec la même complexité.
Preuve du théorème. S’il y a un arc de retour (x, y) alors le chemin de y vers x dans
l’arbre de parcours et l’arc (x, y) forment le circuit.
Dans le sens inverse supposons qu’il existe un circuit dans G et que v est le premier sommet
du circuit visité pendant le parcours. Cela implique que
début[v] < début[x]
pour chaque sommet x du circuit différent de v (rappel : début et fin donnent les dates du
début et de la fin de traitement des sommets). Par la propriété des intervalles de traitement
cela implique que
1. soit début[v] < début[x] < fin[x] < fin[v]
2. soit début[v] < fin[v] < début[x] < fin[x]
Examinons la situation au moment fin[v] quand v devient noir. Pour chaque sommet x de
circuit dans le cas 1 x est noir, dans le cas 2 x est blanc (en particulier pas de sommets gris
en ce moment sur le circuit). Mais s’il y des sommets blanc en ce moment sur le circuit alors
il y a un arc (t, u) sur le circuit avec t noir et u blanc, ce qui n’est pas possible. Alors tous
les sommets sont noirs et tous les sommets du circuits satisfont 1, en particulier le sommet
x qui précède directement v sur le circuit. Mais alors l’arc (x, v) est un arc de retour.
3 On regarde cette fois un parcours en largeur. Il s’agit d’une simple modification du parcours
classique dans lequel on se rappelle du père (pour reconstruire le circuit).
booléen circuit(Sommet s){
Variables :
booléen visité[V]
initialise a faux
sommet père[V]
initialise a null
sommets v,x
File file initialisé vide
enfiler(s,file)
visité[s] = vrai
tant que ( file.nonVide() ) faire{
v = defiler(file)
pour chaque sommet x tel que (v,x) un arc de G faire{
si( !visité[x] ){
visité[x] = vrai
enfiler(x,file)
père[x] = v
profondeur[x]=1+profondeur[v]
}
sinon si ( x == s){
tant que ( v != null ) faire{
afficher v
v = père[v]
}
retourner vrai
}
} // fin pour
} // fin tant que
retourner faux
}
– Tant qu’on ne rentre pas dans le sinon, les Invariants du cours sur le parcours en largeur
restent corrects, en particulier sur le fait qu’on a déjà visité les sommets de profondeur
strictement inférieure (a), et que les chemins des sources lient bien v0 à v (b).
– Au moment où on rentre dans le sinon, on a donc un circuit sur s (b). Et on sait que
tout sommet vu précédemment, (donc de profondeur inférieur) n’est pas lié a s. D’où la
minimalité de la profondeur de ce circuit. (si on s’arrête c’est correct).
– On considère, parmi les circuits de profondeur minimale, les sommets de plus grande
profondeur. Parmi ceux la, l’un d’eux sera le premier atteint pour un parcours en largeur
donné. Pour celui la on rentrera dans le sinon, on trouvera le circuit, ça termine.
– S’il n’y a pas de circuit, on ne rentrera jamais dans le sinon. Tout ce passera comme un
parcours en largeur classique, ça termine.
4 Oui, c’est suffisant.
Correction 8 : Un tel graphe est un arbre ! On les reconnaı̂t, par exemple, au fait que toutes
les arêtes doivent être de parcours, dans un parcours en largeur (ou en profondeur), et qu’ils sont
connexe. Donc on fait :
Fonction TesteArbre (G : graphe)
Variables:
booleen marquage[V] initialise a faux
sommet v,y
entier i=0
File Afaire
Debut :
Prendre un sommet v quelconque
marquage[v]=vrai
enfiler (v,Afaire)
tant que (Afaire non vide) faire
i++
v=defiler(Afaire)
pour chaque voisin y de v faire
si marquage[y]==faux faire
enfiler[y]
marquage[y]=vrai
sinon
retourner faux
retourner (i==n) (c’est-a-fire on a extrait n sommets de la file)
Fin.
Téléchargement