TP algorithme parcours graphes

Utiliser un outil en ligne

Pour une première approche du traitement sur un graphe: Ouvrir l’application en ligne https://graphonline.ru/fr/

Plus court chemin

Vous pourrez alors créer un premier graphe de type: sous-réseaux en étoile. Ce graphe pourrait être la représentation d’un réseau social, ou bien de 2 sous-réseaux interfacés par un routeur.

Une fois le graphe réalisé, explorer le menu des Algorithmes, et sélectionner:

  • le degré des sommets
  • le rayon du graphe
  • l’arbre couvrant minimal
  • la Recherche du plus court chemin entre 2 sommets du graphe.

Combien d’arêtes séparent les sommets les plus éloignés de ce graphe?

Chemin Eulérien

La page wikipedia présente ce qu’est un chemin eulérien.

Représenter chacun des 2 graphes suivants, l’un après l’autre, et chercher la présence (ou non) d’un chemin eulérien dans une telle figure.

Q.a: Comment modifier (à minima) le graphe 2 pour qu’il présente un chemin eulérien?

Traitement des graphes en Python

Dessiner le graphe avec la librairie networkx

Dans un notebook python, saisir les lignes:

import matplotlib.pyplot as plt
import networkx as nx
from numpy import array
G = nx.Graph()
# definition des noeuds
G.add_node(0,label='A',col='white')
G.add_node(1,label='B',col='white')
G.add_node(2,label='C',col='white')
G.add_node(3,label='D',col='white')

# definition des aretes
G.add_edge(0,1)
G.add_edge(0,2)
G.add_edge(2,3)

Rappel:

  • Anglais: Node; Français: Sommet (noeud)
  • Anglais: Edge; Français: Arête

Puis, dans une nouvelle cellule, ajouter les 3 parties suivantes:

  • Dessin des sommets
# calcul des positions pour repartir les sommets du graphe
pos = nx.spring_layout(G)  

# dessin des sommets
L = list(G.nodes(data='col'))
colorNodes = [node[1] for node in L]
nx.draw_networkx_nodes(G, pos, node_size=700,node_color=colorNodes,alpha=0.9)
  • Afficher les étiquettes
# labels
labels_nodes={node:label for node,label in G.nodes(data='label')}

nx.draw_networkx_labels(G, pos, labels=labels_nodes, \
                        font_size=20, \
                        font_color='black', \
                        font_family='sans-serif')

  • Dessiner les arêtes
# edges
nx.draw_networkx_edges(G, pos)

plt.axis('off')
plt.show()

Q.b: Modifier maintenant la déclaration du graphe (cellule 1) pour dessiner le graphe suivant:

Interragir avec les attributs du graphe

L’objet graphe G possède des méthodes de type Getter et Setter. Nous allons explorer celles-ci.

Fonctions utiles de networkx

Pour connaitre la liste des sommets et des arêtes:

print(list(G.nodes()))
print(list(G.edges()))

La densité du graphe:

nx.density(G)

Pour connaitre la liste des voisins d’un sommet:

nx.neighbors(G,node)

Pour connaitre la liste des voisins de TOUS les sommets, ainsi que leur couleur:

for node in list(G.nodes()):
    print(node,'->',list(nx.neighbors(G,node)),G.nodes(data='col')[node])

Q.c: Compléter le script pour obtenir le dictionnaire représentant ce graphe:

D = {}
for node in list(G.nodes()):
	# a completer
print (D)

affiche:

{0: [1, 2, 4],
 1: [0, 4],
 2: [0, 4, 3],
 3: [4, 2, 5],
 4: [0, 1, 2, 3, 5],
 5: [4, 3]}

Getter

La documentation de la fonction se trouve ici: networkx.org/documentation

Tester dans une nouvelle cellule:

color = nx.get_node_attributes(G, "col")
print('color',color)

Q.d: Quel est le format de données retourné par get_node_attributes?

Q.e: Afficher la seule couleur du sommet 0, en modifiant cette instruction.

Q.f: Obtenir l’information des attributs label à l’aide d’une instruction similaire.

Setter

La documentation de la fonction se trouve ici: networkx.org/documentation

Tester dans une nouvelle cellule:

nx.set_node_attributes(G, {0:{"col":'blue'}})
color = nx.get_node_attributes(G, "col")
print('color',color)

Q.f: Que remarque t-on? Pourquoi?

Algorithmes de parcours de graphes

Pour une description illustrée de ces 2 parcours dans un graphe, on pourra consulter la page suivante sur le site allophysique.com.

La page suivante du site marcarea.com propose plusieurs implémentations pour les parcours en largeur et en profondeur d’un graphe.

  • Pour utiliser l’une de ces fonctions, il faudra une structure de données de type dictionnaire pour le graphe: revoir le paragraphe Fonctions utiles de networkx.

  • Ajouter une fonction pour dessiner et sauvegarder le graphe dans un fichier:

def dessine(G,filename):
    plt.clf()
    L = list(G.nodes(data='col'))
    colorNodes = [node[1] for node in L]
    nx.draw_networkx_nodes(G, pos, node_size=700, node_color=colorNodes, alpha=0.9)

    # labels
    labels_nodes = {node: label for node, label in G.nodes(data='label')}

    nx.draw_networkx_labels(G, pos, labels=labels_nodes, \
                            font_size=20, \
                            font_color='black', \
                            font_family='sans-serif')

    nx.draw_networkx_edges(G, pos)
    plt.savefig(filename)
  • Adapter ensuite la fonction de recherche pour tracer les graphes au fur et à mesure du parcours. Démarrer du sommet 0:
plt.figure() # utile pour afficher TOUS les graphes
recursive_dfs(D,0)

Correction

import matplotlib.pyplot as plt
import networkx as nx
from numpy import array

def inverse(L):
    return [L[i] for i in range(len(L)-1,-1,-1)]

def dessine(G,filename):
    plt.clf()
    L = list(G.nodes(data='col'))
    colorNodes = [node[1] for node in L]
    nx.draw_networkx_nodes(G, pos, node_size=700, node_color=colorNodes, alpha=0.9)

    # labels
    labels_nodes = {node: label for node, label in G.nodes(data='label')}

    nx.draw_networkx_labels(G, pos, labels=labels_nodes, \
                            font_size=20, \
                            font_color='black', \
                            font_family='sans-serif')

    nx.draw_networkx_edges(G, pos)
    plt.savefig(filename)



G = nx.Graph()
# definition des noeuds
G.add_node(0, label='A', col='white')
G.add_node(1, label='B', col='white')
G.add_node(2, label='C', col='white')
G.add_node(3, label='D', col='white')
G.add_node(4, label='E', col='white')
G.add_node(5, label='F', col='white')
# definition des aretes
G.add_edge(0, 1)
G.add_edge(0, 2)
G.add_edge(0, 4)
G.add_edge(4, 1)
G.add_edge(4, 2)
G.add_edge(4, 3)
G.add_edge(2, 3)
G.add_edge(4, 5)
G.add_edge(3, 5)

# calcul des positions pour repartir les sommets du graphe
pos = nx.spring_layout(G)

# Dictionnaire du graphe
D = {}
for node in list(G.nodes()):
    D[node] = list(nx.neighbors(G, node))
print(D)

# Parcours en profondeur avec coloration des sommets
def dfs(graph, node, visited=None,stack=None):
    compteur_image=0
    if visited is None:
        visited = []
    if stack is None:
        stack = []
    stack.append(node)
    nx.set_node_attributes(G, {node: {"col": 'green'}})
    dessine(G, 'img/figure' + str(compteur_image) + '.png')
    compteur_image+=1
    while stack:
        node = stack.pop()

        if node not in visited :
            #and G.nodes()[node]['col'] != 'red':
            visited.append(node)

            #plt.show()
            unvisited = [n for n in graph[node] if n not in visited]
            stack.extend(inverse(unvisited))
            for n in unvisited :
                nx.set_node_attributes(G, {n: {"col": 'green'}})
            dessine(G, 'img/figure' + str(compteur_image) + '.png')
            compteur_image += 1
            nx.set_node_attributes(G, {node: {"col": 'red'}})
            dessine(G,'img/figure'+str(compteur_image)+'.png')
            compteur_image += 1
    #plt.show()

    return visited


# appel de dfs depuis le sommet 0 et affichage graphique
#plt.figure()
visited = dfs(D, 0)

print(visited)

Liens