Encodage de graphes

Auteur :
  • François Brucker

Comment encoder la structure tableau blanc (ie. tableau noir pour les plus nostalgiques d'entres nous) de graphe en une structure informatique permettant d'utiliser les graphes dans des algorithmes.

Nous montrerons quatre encodages, chacun ayant ses avantages et ses inconvénients.

Nous utiliserons pour chacun de ces encodages le graphe orienté avec boucle ci-après comme exemple :

un graphe orienté

$G = (V, E)$ où :

Les différentes implémentations sont faites en python.

Opérations à implémenter

On se restreint ici à la structure de graphe orienté avec boucle, un graphe (non orienté) étant un cas particulier.

Nous ne nous intéresserons pas à la structure de multi-graphe, qui s'implémente difficilement autrement que par une liste d'arcs.

Lorsque l'on encode une structure en informatique, il faut commencer par définir les opérations que l'on veut pouvoir effectuer. On distingue usuellement des opérations en deux groupes : les opération de construction/suppression et les opérations de manipulation. Pour un graphe :

Il faut ensuite calculer la complexité de chaque opération, qui vont dépendre de l'encodage.

De là :

L'encodage utilisé pour exécuter un algorithme va dépendre des opérations qu'il va effectuer sur la structure.

Encodage par une liste

Structure simple, on utilise deux listes (une pour les sommets, une pour les arcs).

Construction

  V = ['a', 'b', 'c', 'd', 'e']
  E = [('a', 'b'), ('b', 'b'), ('b', 'c'), ('c', 'd'), 
        ('d', 'a'), ('e', 'd'), ('a', 'e'), ('e', 'a')]

Pour ajouter et supprimer des arcs, il faut faire attention. En python ajouter ou supprimer un élément dans une liste dépend de l'endroit où on le fait :

python

Ajouter ou supprimer un élément dans une liste prend :

  • $\mathcal{O}(1)$ opérations si c'est le dernier élément de la liste
  • $\mathcal{O}(\vert L\vert)$ opérations si c'est un élément quelconque (il faut décaler les éléments après l'élément ajouté/supprimé)

On essaiera toujours d'ajouter/supprimer des éléments en fin de liste.

Opérations

Structure de stockage la plus simple. N'est optimisé pour aucune opération spécifique :

Ce n'est pas parce qu'en python on peut écrire 'a' in V que sa complexité est $\mathcal{O}(1)$... Il faut parcourir toute la liste V pour savoir si 'a' y est.

Encodage par une liste d'adjacence

Structure plus complexe que la liste, elle nécessite un re-codage des sommets sous la forme d'entiers pour fonctionner.

Construction

  V = ['a', 'b', 'c', 'd', 'e']
  E = [[1, 4], [1, 2], [3], [0], [3, 0]]

Dans $E$ chaque sommet est désigné par son indice dans $V$ et $E[i]$ est le voisinage sortant du sommet $i$.

Pour utiliser cette structure, on considère que les sommets sont des entiers allant de $0$ à $n-1$. La liste $V$ n'est là que pour pouvoir associer plus tard un sommet à autre chose qu'un entier (dépendant de l'application).

Ajout/suppression de sommets/arcs :

On utilise souvent une variante de cette structure qui utilise des tableaux associatifs à la place des listes. Voir par exemple l'implémentation en python. On troque alors les complexités maximale par des complexités en moyennes, mais on a plus besoin de l'encodage des éléments sous la forme d'entiers.

Opérations

L'intérêt de cette encodage est que certaines opérations sont optimisées :

Encodage par matrice d'adjacence

Tout comme la liste d'adjacence, cette structure nécessite un re-codage des sommets sous la forme d'entiers pour fonctionner.

Construction

V = ['a', 'b', 'c', 'd', 'e']
E = [[0, 1, 0, 0, 1], 
     [0, 1, 1, 0, 0], 
     [0, 0, 0, 1, 0],
     [1, 0, 0, 0, 0],
     [1, 1, 0, 1, 1]]

Dans $E$ chaque sommet est désigné par son indice dans $V$ et $E[i][j]$ vaut $1$ si $(i, j)$ est un arc et 0 sinon.

Pour utiliser cette structure, on considère que les sommets sont des entiers allant de $0$ à $n-1$. La liste $V$ n'est là que pour pouvoir associer plus tard un sommet à autre chose qu'un entier (dépendant de l'application).

Ajout/suppression de sommets/arcs :

Cet encodage permet de traiter les graphes valués (la valeurs de $E[i][j]$ est la valuation de l'arête $xy$).

Opérations

L'intérêt de cette encodage est que le fait de savoir si un arête est présente dans le graphe est optimisé :

Encodage par dictionnaire

C'est le codage canonique des graphes en python. Il ressemble fortement au codage par liste d'adjacence, mais ne nécessite pas de ré-encodage des sommets.

Construction

G = {
  'a': {'b', 'e'},
  'b': {'b', 'c'},
  'c': {'d'},
  'd': {'a'},
  'e': {'a', 'd'},
}

On utilise à la fois un dictionnaire pour stocker le voisinage de chaque éléments, lui même codé sous la forme d'un ensemble.

On remplace parfois l'ensemble de voisinage par une liste de voisinage. Cela augmente cependant la complexité de savoir si un élément est un voisin.

Opérations

L'intérêt de cette encodage est que l'on arrive à obtenir le meilleurs des deux mondes en moyenne.

Quand utiliser quoi ?

Selon ce qu'on a besoin de faire, on utilisera plutôt une structure de donnée qu'une autre, voir changera de structure si le passage d'une structure de données à l'autre est simple.

On remarque tout de suite que les trois premières structures sont mauvaise pour ajouter et/ou supprimer un sommet dans un graphe. On ne pourra donc pas les utiliser dans des applications où le graphe a un nombre variable de sommets au court du temps. Heureusement, ce genre d'application est peu courante. La dernière structure est optimale, mais seulement en moyenne. Si l'on cherche la performance garantie, ce n'est donc pas vers ce genre de structure qu'il faut s'orienter.

Utilisation de l'encodage en liste

Quand utiliser cette structure ?

solution

Cette structure est optimale pour le stockage, mais très mauvaise pour tout le reste. On l'utilise donc quasi-exclusivement comme moyen de stocker ou de transmettre un graphe.

Utilisation de l'encodage par liste d'adjacence

Quand utiliser cette structure ?

solution

Optimale pour parcourir un graphe dont les arc sont fixés. On utilisera cette structure lorsque l'algorithme très souvent parcourir les voisinages de sommets d'un graphe fixe.

Utilisation de l'encodage par matrice d'adjacence

Quand utiliser cette structure ?

solution

Optimale pour ajouter/supprimer des arc et savoir si un arc est présent ou non. On utilisera cette structure lorsque l'algorithme très souvent modifier les arcs du graphe et/ou savoir si un arc est présent ou non.

Utilisation de l'encodage par dictionnaire

Quand utiliser cette structure ?

solution

Lorsque la complexité maximale la plus faible n'est pas recherchée mais que l'on veut obtenir de bonnes performances dans le cas général.

Dans la suite de ce cours on utilisera toujours cet encodage par défaut car elle est efficace globalement et facile à implémenter (elle ne nécessite pas de ré-encodage).

Bibliothèques

Il existe plusieurs bibliothèques de gestion de graphes en python. Cette page en cite trois, activement maintenues.