Vue d'ensemble
Un graphe modélise des objets et des liens entre eux : des villes reliées par des routes, des pages web par des liens, des personnes par des amitiés, des tâches par des dépendances. C'est l'une des structures les plus puissantes de l'informatique, et le support de grands algorithmes de deuxième année (parcours, plus court chemin). Avant de les parcourir, il faut deux choses : le vocabulaire exact (sommet, arête, degré, chemin, cycle…) et la façon de représenter un graphe en machine. Cette fiche pose ce socle, code les deux représentations classiques — listes d'adjacence et matrice d'adjacence —, les décrypte ligne par ligne et compare leurs coûts.
Prérequis
- Manipuler une liste Python : parcours
for,len, testx in liste(fiche « listes et chaînes ») - Manipuler un dictionnaire : clé → valeur, parcours
for k in d(fiche « dictionnaires ») - Savoir lire un coût en , (fiche « complexité temporelle »)
Les graphes font peur, mais tout part d'un vocabulaire précis et de deux représentations. Confondre arête et arc, ou choisir la mauvaise représentation, coûte cher aux concours. Nos mentors alumni X · Centrale · Mines t'apprennent à dessiner, coder et raisonner sur un graphe jusqu'à ce que la structure devienne intuitive.
Trouver un mentor →1. Le vocabulaire des graphes
Un graphe est la donnée d'un ensemble de sommets (ou nœuds) et d'un ensemble de liens entre ces sommets. On note traditionnellement le nombre de sommets et le nombre de liens. Deux cas :
- Graphe non orienté : les liens sont des arêtes, symétriques. Une arête entre et se note : elle relie à et à .
- Graphe orienté : les liens sont des arcs, à sens unique. Un arc de vers se note : il va de à , pas l'inverse.
Prenons un graphe non orienté à quatre sommets et quatre arêtes : , , , . Il nous servira de fil rouge dans toute la fiche.
Deux sommets reliés par une arête sont voisins (ou adjacents). Le degré d'un sommet est son nombre de voisins (le nombre d'arêtes qui le touchent). Dans notre exemple, a pour voisins : son degré vaut 3. Dans un graphe orienté, on distingue le degré sortant (nombre d'arcs qui partent du sommet) et le degré entrant (nombre d'arcs qui arrivent).
Un chemin est une suite de sommets consécutivement reliés : est un chemin de à , de longueur 2 (on compte les arêtes). Un cycle est un chemin qui revient à son point de départ sans réemprunter une arête : est un cycle. Un graphe est connexe si l'on peut aller de n'importe quel sommet à n'importe quel autre par un chemin (le graphe est « d'un seul tenant »).
2. Représentation par listes d'adjacence
Première façon de mettre un graphe en machine : pour chaque sommet, on stocke la liste de ses voisins. En Python, c'est naturellement un dictionnaire qui associe à chaque sommet la liste de ses voisins.
g = {
"A": ["B", "C"],
"B": ["A", "C"],
"C": ["A", "B", "D"],
"D": ["C"],
}
print(g["C"]) # ['A', 'B', 'D'] : les voisins de C
print(len(g["C"])) # 3 : le degre de C
print("D" in g["A"]) # False : il n'y a pas d'arete entre A et Dg = { "A": ["B", "C"], ... }Un dictionnaire sommet → voisins. Chaque clé est un sommet, chaque valeur est la liste de ses voisins. L'arête apparaît deux fois : "B" est dans g["A"] ET "A" est dans g["B"]. C'est ce qui traduit la symétrie du non orienté.g["C"]Les voisins d'un sommet, directement. On lit la liste des voisins de C en une seule opération : ['A', 'B', 'D']. C'est le grand atout de cette représentation.len(g["C"])Le degré. Le degré d'un sommet est le nombre de ses voisins, donc la longueur de sa liste : ici 3."D" in g["A"]Tester une arête. L'arête existe si "D" figure dans la liste des voisins de A. Ici False. Ce test parcourt la liste : il coûte , le degré de .Écrivons les trois opérations de base comme des fonctions, puis déroulons-les sur le fil rouge.
def voisins(g, s):
return g[s] # la liste des voisins de s
def degre(g, s):
return len(g[s]) # nombre de voisins
def nb_aretes(g):
total = 0
for s in g: # on additionne tous les degres
total = total + len(g[s])
return total // 2 # chaque arete est comptee 2 fois
print(degre(g, "A"), degre(g, "B"), degre(g, "C"), degre(g, "D")) # 2 2 3 1
print(nb_aretes(g)) # 4return g[s]Voisins. On renvoie telle quelle la liste stockée pour s — coût .total = total + len(g[s])On somme les degrés. On parcourt tous les sommets et on ajoute la longueur de chaque liste de voisins : on obtient la somme des degrés.return total // 2Le piège du double comptage. Chaque arête est comptée une fois dans le degré de et une fois dans celui de . La somme des degrés vaut donc deux fois le nombre d'arêtes : on divise par 2. (C'est le lemme démontré au §4.)Sommet s | g[s] | len(g[s]) | total après |
|---|---|---|---|
| A | ['B', 'C'] | 2 | 2 |
| B | ['A', 'C'] | 2 | 4 |
| C | ['A', 'B', 'D'] | 3 | 7 |
| D | ['C'] | 1 | 8 |
résultat : total // 2 | 8 // 2 = 4 ✓ | ||
3. Représentation par matrice d'adjacence
Deuxième façon : on numérote les sommets et on stocke un tableau carré de 0 et de 1. La case ligne , colonne vaut 1 s'il y a une arête entre le sommet et le sommet , 0 sinon. En Python, c'est une liste de listes.
Avec l'ordre numérotés , notre graphe donne :
# ordre des sommets : A=0, B=1, C=2, D=3
M = [
[0, 1, 1, 0], # A relie a B et C
[1, 0, 1, 0], # B relie a A et C
[1, 1, 0, 1], # C relie a A, B et D
[0, 0, 1, 0], # D relie a C
]
print(M[0][1]) # 1 : il y a une arete entre A (0) et B (1)
print(M[0][3]) # 0 : pas d'arete entre A (0) et D (3)
print(sum(M[2])) # 3 : le degre de C = somme de sa ligneM = [[0, 1, 1, 0], ...]Une grille de 0 et 1. M[i] est la ligne du sommet ; M[i][j] vaut 1 si et sont reliés. La matrice est symétrique (M[i][j] == M[j][i]) car le graphe est non orienté.M[0][1]Tester une arête en . On lit directement la case : y a-t-il une arête entre (0) et (1) ? La valeur 1 dit oui. Aucun parcours, contrairement aux listes d'adjacence.M[0][3]Absence d'arête. La case vaut 0 : pas d'arête entre et . Cohérent avec le §2.sum(M[2])Le degré par somme de ligne. Le degré du sommet est le nombre de 1 sur sa ligne, donc sum(M[i]). Pour (ligne 2) : . Ce calcul coûte (on lit toute la ligne).M[i][i] vaut 0 tant qu'il n'y a pas de boucle
(une arête d'un sommet vers lui-même). En orienté, la matrice n'est en général pas symétrique :
M[i][j] = 1 signale l'arc , et M[j][i] peut valoir 0.
4. Quelle représentation choisir ?
Les deux représentations décrivent le même graphe, mais n'ont pas les mêmes coûts. Le choix dépend de la densité du graphe (beaucoup d'arêtes, ou peu) et des opérations qu'on veut faire souvent.
| Critère | Listes d'adjacence | Matrice d'adjacence |
|---|---|---|
| Mémoire | ||
| Tester une arête | ||
| Parcourir les voisins de | ||
| Idéale pour… | graphe creux (peu d'arêtes) | graphe dense (beaucoup d'arêtes) |
- Le graphe est creux ( petit devant , comme un réseau routier) et on parcourt souvent les voisins : listes d'adjacence ( mémoire).
- Le graphe est dense ou on teste très souvent « telle arête existe-t-elle ? » : matrice d'adjacence (test en ).
- En cas de doute aux concours, les listes d'adjacence sont le choix par défaut : les vrais graphes sont presque toujours creux.
Terminons par un résultat qui relie degrés et arêtes, déjà croisé au §2 avec la division par 2.
Dans un graphe non orienté à arêtes, la somme des degrés de tous les sommets vaut En particulier, cette somme est toujours paire.
Démonstration
Comptons de deux façons le nombre de couples où est un sommet et une arête qui touche (on dit que est une extrémité de ).
Premier décompte, par sommet. Pour un sommet fixé, le nombre d'arêtes qui le touchent est, par définition, son degré . En sommant sur tous les sommets, le nombre de couples vaut .
Second décompte, par arête. Une arête a exactement deux extrémités, et . Elle fournit donc exactement 2 couples. En sommant sur les arêtes, le nombre de couples vaut .
Les deux décomptes comptent la même chose, donc . Comme est pair, la somme des degrés est paire.
nb_aretes en
divisant la somme des degrés par 2.
Le « double comptage » est une technique de preuve qui revient partout en info comme en maths. Nos mentors alumni X · Centrale · Mines t'entraînent à ce type de raisonnement — celui qui fait la différence sur les questions de cours à l'oral.
Trouver un mentor →5. Exercices d'application
Fais-les sur papier (dessine le graphe !) avant d'ouvrir le corrigé, puis passe au quiz en bas de fiche.
Un graphe non orienté a pour sommets et pour arêtes , , . Donne sa représentation par listes d'adjacence (un dictionnaire Python), puis le degré de chaque sommet.
Voir la correction détaillée
g = {
1: [2, 3],
2: [1, 3],
3: [1, 2],
}len(g[1]) = len(g[2]) = len(g[3]) = 2. Somme des degrés : cohérent, il y a bien 3 arêtes.Écris voisins_communs(g, u, v) qui renvoie la liste des sommets voisins à la fois de u et de v (représentation par listes d'adjacence). Teste sur le graphe fil rouge avec u = "A", v = "B".
Voir la correction détaillée
x est voisin commun s'il est dans g[u] ET dans g[v].def voisins_communs(g, u, v):
communs = []
for x in g[u]:
if x in g[v]:
communs.append(x)
return communs
print(voisins_communs(g, "A", "B")) # ['C']A : ['B', 'C'] ; voisins de B : ['A', 'C']. Le seul commun est C → ['C']. ( et forment un triangle avec .)On dispose d'une matrice d'adjacence M (liste de listes , 0/1) et de la liste
noms des sommets dans l'ordre des indices. Écris matrice_vers_listes(M, noms) qui
renvoie le dictionnaire de listes d'adjacence correspondant.
Voir la correction détaillée
i, on parcourt sa ligne M[i] ; dès que M[i][j] == 1, le sommet noms[j] est un voisin de noms[i].def matrice_vers_listes(M, noms):
n = len(M)
g = {}
for i in range(n):
g[noms[i]] = []
for j in range(n):
if M[i][j] == 1:
g[noms[i]].append(noms[j])
return g
M = [[0,1,1,0],[1,0,1,0],[1,1,0,1],[0,0,1,0]]
print(matrice_vers_listes(M, ["A","B","C","D"]))
# {'A': ['B', 'C'], 'B': ['A', 'C'], 'C': ['A', 'B', 'D'], 'D': ['C']}g du §2. Deux boucles imbriquées sur : la conversion coûte — le prix à payer pour lire toute la matrice.Récap final — Ce qu'il faut absolument retenir
À la veille d'une khôlle ou d'un DS, parcours cette checklist : tu dois pouvoir répondre « oui, sans hésiter » à chaque question.
- Sais-tu distinguer graphe orienté (arcs, ) et non orienté (arêtes, ) ?
- Sais-tu définir voisins, degré (et degrés entrant / sortant en orienté), chemin, cycle, connexité ?
- Sais-tu représenter un graphe par listes d'adjacence (dictionnaire sommet → voisins) ?
- Sais-tu représenter un graphe par matrice d'adjacence et lire un degré comme une somme de ligne ?
- Sais-tu que les listes coûtent en mémoire et la matrice ?
- Sais-tu que tester une arête est en matrice mais en listes ?
- Sais-tu pourquoi
nb_aretesdivise la somme des degrés par 2 ? - Sais-tu énoncer et démontrer le lemme des poignées de main () ?
À savoir refaire
- Lemme des poignées de main — double comptage des couples (sommet, arête incidente) : .