Cours - LIFL

publicité
Algorithmique Avancée et Complexité
UFR IEEA
Parcours de Graphes
20052006
Master 1 d'Informatique
S.Tison
Il y a une grande diversité de problèmes et d'algorithmes concernant les graphes. Cependant, souvent ces
algorithmes ont en commun le fait qu'ils eectuent une recherche dans le graphe ou un parcours du graphe:
l'algorithme visite systématiquement (tout du moins jusqu'à avoir trouvé une solution) les noeuds (ou arcs)
du graphe et eectue un certain traitement pour chaque noeud (ou arc). Les algorithmes de parcours d'un
graphe sont donc fondamentaux. Ce sont des généralisations de ceux développés pour les arbres, la diculté
supplémentaire consistant à éviter les cycles.
Le schéma dans le cas des arbres
Dans un arbre A, le schéma général d'un algorithme de parcours à partir d'un noeud racine est:
parcours (racine){//parcourt les noeuds de A accessibles à partir du noeud source
//un noeud pourra etre soit Nondécouvert, Adévelopper
//debut INIT
marquer tous les noeuds Nondécouvert;
marquer racine Adévelopper;
//fin INIT
tant qu'il existe un noeud Adévelopper (et Non Fin)
prendre un noeud Adévelopper;
le traiter; //éventuellement mettre Fin à Vrai
marquer tous ses successeurs (ou voisins) comme Adévelopper;
fin tantque;
}
Les diérents parcours dépendent essentiellement de la façon dont on gère l'ensemble des noeuds à développer:
pile, le, le de priorité... Dans le cas des graphes, la situation est un peu plus complexe, car un noeud peut
être successeur de plusieurs noeuds...
Le parcours d'un graphe en profondeur d'abord
Ce parcours peut être vu comme le classique parcours selon la règle de la main gauche dans un labyrinthe. Il
est souvent appelé DFS (pour depth-rst-search). Une version récursive peut être donnée par:
//un noeud sera ici soit Nondécouvert, soit Enexploration soit Exploré;
visiteenprofondeur(G,départ) {//visite en profondeur d'abord tout le sous-graphe issu de départ
marquer tous les noeuds Nondécouvert;
t=O; //t permettra de marquer les noeuds avec l'heure de début et de fin d'exploration
visite(G,départ);}
//avec:
visite(G,s) {//visite en profondeur d'abord s et le sous-graphe Nondécouvert issu de s
t++;
deb(s)=t;
marquer s Enexploration;
traiter s;
pour chaque noeud n successeur (ou voisin) de s
si n Nondécouvert {parent(n)=s;visiter(n)} fin si;
fin pour;
t++;
fin(s)=t;
marquer s Exploré;
}
}
cet algorithme ne parcourt que les noeuds accessibles à partir de départ, la composante connexe
de départ dans le cas non orienté. Pour parcourir tout le graphe si il n'est pas connexe, il faudra donc faire:
Attention:
marquer tous les noeuds Nondécouvert;
pour tout s //dans un ordre arbitraire
si s Nondécouvert alors visite(G,s);
fin pour;
//visite défini comme ci-dessus
La version itérative -qui correspond à la dérécursivation de la version récursive- est obtenue à partir du
schéma général en utilisant une pile (LIFO: LAst-in First-Out) pour gérer les noeuds à développer:
visiteIter(G, source){//un noeud pourra etre soit Nondécouvert, Adévelopper, Exploré
pile.init(); //initialiser pile à vide, pile contiendra les noeuds Adévelopper
marquer tous les noeuds Nondécouvert;
marquer source Adévelopper;
pile.ajouter(source);
tant que pile.nonvide(){
n=pile.sommet();
t++;
debut(n)=t;
traiter(n);
si il existe noeud un successeur de n Nondécouvert
alors {marquer noeud comme Adévelopper;
parent(noeud)=n;
pile.ajouter(noeud);}
sinon {t++;marquer n exploré;fin(n)=t; dépiler();}
fin si;}
fin tantque;}
Comme dans la version récursive, cet algorithme ne visite que les noeuds accessibles à partir de la source!
On peut par exemple utiliser ce schéma pour juste chercher si il existe un chemin de source à un noeud
donné but. La recherche se fera en profondeur mais s'arrêtera dès que le noeud but a été atteint. On peut aussi
l'utiliser pour chercher la composante connexe du noeud source ou pour calculer les composantes connexes d'un
graphe. Cet algorithme peut aussi être utilisé pour le tri topologique d'un graphe acyclique (c.à.d. trouver un
ordre total sur les noeuds tel qu'un noeud soit plus petit que ses successeurs).
Le parcours d'un graphe en largeur d'abord.
Le parcours en largeur d'abord (BFS pour Breadth-rst-search) utilise une le (FIFO:First-In First-OUT) pour
gérer les noeuds à développer. Dans un arbre, cela correspond à parcourir l'arbre par niveau; dans les graphes
aussi, si on considère que le niveau d'un noeud est la longueur du chemin le plus court à partir de la source.
parcoursenlargeur(G, source){//un noeud pourra etre soit Nondécouvert, Adévelopper, Exploré
file.init(); //initialiser file à vide, file contiendra les noeuds Adévelopper
marquer tous les noeuds Nondécouvert;
marquer source Adévelopper;
file.ajouter(source);
tant que file.nonvide(){
file.enlever(n);
traiter(n);
marquer n comme Exploré;
pour chaque noeud successeur de n
si noeud est Nondécouvert
alors { marquer noeud comme Adévelopper;
file.ajouter(noeud);}
fin si;
fin pour;}
fin tantque;
}
2
Téléchargement