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