Vous êtes sur la page 1sur 9

MATH SPE MP

PISSARRO PONTOISE
2022-2023
Bases des graphes, plus courts chemins

Programme d’informatique commune


Vocabulaire des graphes.
Graphe orienté, graphe non orienté. Sommet (ou nœud) ; arc, arête. Boucle. Degré (entrant et sortant).
Chemin d’un sommet à un autre. Cycle. Connexité dans les graphes non orientés.
On présente l’implémentation des graphes à l’aide de listes d’adjacence (rassemblées par exemple dans
une liste ou dans un dictionnaire) et de matrice d’adjacence. On n’évoque ni multi-arcs ni multi-arêtes.
Notations.
Graphe G = (S, A), degrés d(S) (pour un graphe non orienté), d+ (S) et d− (S) (pour un graphe orienté).
Pondération d’un graphe. Étiquettes des arcs ou des arêtes d’un graphe.
On motive l’ajout d’information à un graphe par des exemples concrets.
Parcours d’un graphe. On introduit à cette occasion les piles et les files ; on souligne les problèmes
d’efficacité posés par l’implémentation des files par les listes de Python et l’avantage d’utiliser un module
dédié tel que collections.deque.
Détection de la présence de cycles ou de la connexité d’un graphe non orienté.
Recherche d’un plus court chemin dans un graphe pondéré avec des poids positifs. Algorithme de Dijkstra.
On peut se contenter d’un modèle de file de priorité naı̈f pour extraire l’élément minimum d’une collection.
Sur des exemples, on s’appuie sur l’algorithme A* vu comme variante de celui de Dijkstra pour une
première sensibilisation à la notion d’heuristique.

Table des matières


1 Exemples 2

2 Graphe orienté 2

3 Graphe non orienté 2

4 Implémentation d’un graphe en Python 3


4.1 Liste d’adjacence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4.2 Matrice d’adjacence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

5 Piles, files 4
5.1 Les piles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
5.2 Les files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
5.3 Le module deque . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

6 Algorithme de parcours d’un graphe 5

7 Application à la détection de la connexité d’un graphe non orienté 6

8 Plus court chemin 6

9 Graphe pondéré 7

10 Plus court chemin, algorithme de Dijkstra 8

11 Une amélioration : l’algorithme A* 9

1
1 Exemples
RATP, SNCF, EDF, Réseau routier, Facebook, Web, Google Maps, · · ·

2 Graphe orienté
3 1 5

7 6

G = (S, A) avec S = {1, 2, 3, 4, 5, 6, 7} et A = {(1, 7), (7.1), (7, 2), (1, 2), (7, 4), (3, 1), (5, 6), (6, 5)}
On peut représenter G par sa liste d’adjacence :
1 → [2, 7], 2 → [ ], 3 → [1], 4 → [ ], 5 →
 [6], 6 → [5], 7 → [1, 2,4]
0 1 0 0 0 0 1
0 0 0 0 0 0 0
 
1 0 0 0 0 0 0
 
ou par sa matrice d’adjacence : M =  0 0 0 0 0 0 0

0 0 0 0 0 1 0
 
0 0 0 0 1 0 0
1 1 0 1 0 0 0

Vocabulaire
graphe orienté : couple (S, A) ou S est un ensemble fini non vide et A un ensemble de couples
d’éléments de S
sommet (ou nœud) : élément de S
arc (ou arête orientée) : élément de A
boucle : arc de la forme (x, x)
degré entrant d’un sommet s : nombre d’arcs qui arrivent à ce sommet, (c’est à dire nombre d’arcs
de la forme (x, s), notation : d− (s)
degré sortant d’un sommet s : nombre d’arcs qui partent de ce sommet, (c’est à dire nombre d’arcs
de la forme (s, x), notation : d+ (s)
chemin d’un sommet à un autre : suite de sommets s0 , s1 , · · · , sp tels que pour tout i ∈ J1, pK,
(ai−1 , ai ) ∈ A
cycle : chemin s0 , s1 , · · · , sp avec s0 = sp
listes d’adjacence : pour chaque sommet s, on donne la liste des sommets où arrive un arc partant
de s (liste des successeurs de s)
matrice d’adjacence : si S = J1, nK, matrice M = (mij ) ∈ Mn (R) avec mij = 1 si (i, j) ∈ A et
mij = 0 sinon.

3 Graphe non orienté


3 1 5

7 6

2
G = (S, A) avec S = {1, 2, 3, 4, 5, 6, 7} et A = {{7, 1}, {7, 2}, {1, 2}, {7, 4}, {3, 1}, {5, 6}, }
On peut représenter G par sa liste d’adjacence :
1 → [2, 3, 7], 2 → [1, 7], 3 → [1], 4 → [7],
 5 → [6], 6 → [5], 7 →[1, 2, 4],
0 1 1 0 0 0 1
 1 0 0 0 0 0 1
 
 1 0 0 0 0 0 0
 
ou par sa matrice d’adjacence : M =   0 0 0 0 0 0 1

 0 0 0 0 0 1 0
 
 0 0 0 0 1 0 0
1 1 0 1 0 0 0

Vocabulaire
graphe non orienté : couple (S, A) ou S est un ensemble fini non vide et A un ensemble de paires
d’éléments de S (deux à deux distincts)
sommet (ou nœud) : élément de S
arête : élément de A
degré d’un sommet s : nombre d’arêtes d’extrémité ce sommet, (c’est à dire nombre d’arêtes de la
forme {x, a}, notation : d(s)
chemin d’un sommet à un autre : suite de sommets s0 , s1 , · · · , sp tels que pour tout i ∈ J1, pK,
{ai−1 , ai } ∈ A
connexité dans les graphes non orientés : on dit qu’un graphe non orienté est connexe quand deux
sommets quelconques sont reliés par un chemin.
listes d’adjacence : pour chaque sommet s, on donne la liste des voisins de s
matrice d’adjacence : si S = J1, nK, matrice M = (mij ) ∈ Mn (R) avec mij = 1 si {i, j} ∈ A et
mij = 0 sinon. M est une matrice symétrique.

Graphe orienté associé à un graphe non orienté


3 1 5 3 1 5

2 2

4 4

7 6 7 6

4 Implémentation d’un graphe en Python


4.1 Liste d’adjacence
Exemple :
1 → [2, 7], 2 → [ ], 3 → [1], 4 → [ ], 5 → [6], 6 → [5], 7 → [1, 2, 4]
Représentation par un dictionnaire :
{ 1 : [2,7], 2 : [ ], 3 : [1], 4 : [ ], 5 : [6], 6 : [5], 7 : [1,2,4]}

4.2 Matrice d’adjacence


 
0 1 0 0 0 0 1
0 0 0 0 0 0 0
 
1 0 0 0 0 0 0
 
Exemple : M =  0 0 0 0 0 0 0
0 0 0 0 0 1 0
 
0 0 0 0 1 0 0
1 1 0 1 0 0 0
Représentation par une liste de listes :

3
[[0,1,0,0,0,0,1],[0,0,0,0,0,0,1],[1,0,0,0,0,0,0],[0,0,0,0,0,0,0],[0,0,0,0,0,1,0],[0,0,0,0,1,0,0],[1,1,0,1,0,0,0]]

5 Piles, files
5.1 Les piles
• Structure de type LIFO (Last In First Out)
• Opérations
◦ Créer une pile vide
◦ Ajouter (empiler) un élément
◦ Enlever et retourner un élément (dépiler)
◦ Tester si une pile est vide
• Python : implémentation par une liste
◦ Créer une pile vide : P = []
◦ Ajouter (empiler) un élément a : P.append(a)
◦ Enlever et retourner un élément (dépiler) : x = P.pop()
◦ Tester si une pile est vide : if P == []
Toutes ces opérations se font en temps constant (indépendant de la longueur de la liste) donc
efficacité.

5.2 Les files


• Structure de type FIFO (First In First Out)
• Opérations
◦ Créer une file vide
◦ Ajouter un élément
◦ Enlever et retourner un élément
◦ Tester si une file est vide
• Python : implémentation par une liste
◦ Créer une file vide : F = []
◦ Ajouter un élément a : F = [a]+F
◦ Enlever et retourner un élément : x = F.pop()
◦ Tester si une file est vide : if F == []
L’ajout d’un élément ne se fait pas en temps constant (le temps de calcul augmente avec la
longueur de la liste) donc peu efficace.

5.3 Le module deque


deque = double ended queue (file à deux bouts)
Comme une liste, mais l’ajout et la suppression d’un élément se font en temps constant des deux cotés
• Implémentation d’une pile :
from collections import deque
◦ Créer une pile vide : P = deque()
◦ Ajouter (empiler) un élément a : F.append(a)
◦ Enlever et retourner un élément (dépiler) : x = F.pop()
◦ Tester si une pile est vide : if len(F) == 0
• Implémentation d’une file :
from collections import deque
◦ Créer une file vide : F = deque()
◦ Ajouter un élément a : F.append(a)
◦ Enlever et retourner un élément : x = F.popleft()
◦ Tester si une file est vide : if len(F) == 0
• Exemple d’utilisation :
from collections import deque
F = deque()
for i in range(3):
F.append(i)
print(F)
for i in range(10):

4
if len(F) != 0:
print(F.popleft())

6 Algorithme de parcours d’un graphe


3 1 5

7 6

On veut explorer le graphe à partir du sommet 1.


Le graphe est donné par sa liste d’adjacence représentée par le dictionnaire :
{ 1:[2,3,7], 2:[6,5], 3:[4], 4:[ ], 5:[6], 6:[5], 7:[1,2,4]}
2 variables :
- aTraiter : une pile ou une file qui contient les sommets à traiter
Au début, aTraiter est vide.
- vu : un dictionnaire qui sert à marquer si un sommet a été vu.
vu[s] vaut True si le sommet a été vu et False sinon.
Au début,
vu={ 1:False, 2:False, 3:False, 4:False, 5:False, 6:False, 7:False, }
• On commence par mettre le sommet 1 dans aTraiter et on le marque comme vu : vu[1]=True
• Ensuite, on répète tant que aTraiter n’est pas vide
◦ on enlève un élément s de aTraiter
◦ on ajoute à aTraiter tous les voisins (successeurs) de s qui n’ont pas encore été vus et on les
marque comme vu

Utilisation de piles
from collections import deque

LA = {1:[2,3,7],2:[6,5],3:[4],4:[],5:[6],6:[5],7:[1,2,4]}

def parcours(listeAdj,s):
""" parcours du graphe à partir du sommet s"""
# création du dictionnaire vu
vu = {}
for sommet in listeAdj:
vu[sommet] = False
# création de la pile ou de la file aTraiter
aTraiter = deque()
aTraiter.append(s)
vu[s] = True
while len(aTraiter)>0:
sommet = aTraiter.pop() # utilisation de piles
for voisin in listeAdj[sommet]:
if not vu[voisin]:
aTraiter.append(voisin)
vu[voisin] = True

Utilisation de files
Comment modifier le programme précédent pour utiliser les files ?
Files : parcours en largeur
Piles : parcours en profondeur

5
7 Application à la détection de la connexité d’un graphe non orienté
3 1 5 3 1 5

2 2

4 4

7 6 7 6

On parcours le graphe à partir du sommet 1, si on voit tous les sommets, le graphe est connexe, sinon, il
ne l’est pas.

from collections import deque

L1 = {1:[2,3,7],2:[1,7],3:[1],4:[7],5:[6],6:[5],7:[1,2,4]}
L2 = {1:[2,3,7],2:[1,5,6,7],3:[1],4:[7],5:[2,6],6:[2,5],7:[1,2,4]}

def estConnexe(listeAdj):
"""retourne True si le graphe est connexe et False sinon"""
# liste des sommets du graphe
S = list(listeAdj.keys())
# création du dictionnaire vu
vu = {}
for sommet in S:
vu[sommet] = False
# création de la pile ou de la file aTraiter
aTraiter = deque()
aTraiter.append(S[0])
vu[S[0]] = True
while len(aTraiter) > 0:
sommet = aTraiter.pop()
for voisin in listeAdj[sommet]:
if not vu[voisin]:
aTraiter.append(voisin)
vu[voisin] = True
for x in S:
if not vu[x]:
return False
return True

print(estConnexe(L1))
print(estConnexe(L2))

8 Plus court chemin


3 1 5

7 6

6
On parcourt le graphe en largeur (utilisation d’une file) à partir du sommet 1. On rajoute un dictionnaire
dist qui donne la distance de chaque sommet au sommet de départ. Quand on visite un sommet s, on
affecte à chacun de ses successeurs non encore visité la distance dist[s]+1
Le sommet 1 est à la distance 0.
Ensuite, on traite tous ses successeurs, ils sont à la distance 1.
Ensuite, on traite tous les successeurs non encore vus des précédents, ils sont à la distance 2.
Et ainsi de suite.

from collections import deque

LA = {1:[2,3,4,7],2:[5,6,7],3:[4],4:[7],5:[6],6:[5],7:[1,2,4,6]}

def distances(listeAdj,s):
""" détermine les distances minimum de chaque sommet au sommet s
retourne un dictionnaire qui à chaque sommet associe sa distance à s"""
# création du dictionnaire vu
vu = {}
for sommet in listeAdj:
vu[sommet] = False
# création du dictionnaire des distances
dist = {}
for sommet in listeAdj:
dist[sommet] = float(’inf’) # flottant de valeur +infini
dist[s] = 0
# création de la pile ou de la file aTraiter
aTraiter = deque()
aTraiter.append(s)
vu[s] = True
while len(aTraiter)>0:
sommet = aTraiter.popleft()
for voisin in listeAdj[sommet]:
if not vu[voisin]:
aTraiter.append(voisin)
vu[voisin] = True
dist[voisin] = dist[sommet]+1
return dist

distances(LA,1) donne
{1: 0, 2: 1, 3: 1, 4: 1, 5: 2, 6: 2, 7: 1}

9 Graphe pondéré
3 3 7
3 1 2 5

5 2 7 5
6 2
4
1
7 6
4

G = (S, A, ρ) avec S = {1, 2, 3, 4, 5, 6, 7}, A = {(1, 7), (7.1), (7, 2), (1, 2), (7, 4), (3, 1), (5, 6), (6, 5), (7, 6), (2, 5), (6, 2)}
et ρ l’application de S dans R+ définie par :
(1, 7) 7→ 5, (7.1) 7→ 2, (7, 2) 7→ 6, (1, 2) 7→ 3, (7, 4) 7→ 1, (3, 1) 7→ 3, (5, 6) 7→ 2, (6, 5) 7→ 5, (7, 6) 7→
4, (2, 5) 7→ 7, (6, 2) 7→ 7
On peut représenter G par sa liste d’adjacence :
1 → [(2, 3), (7, 5)], 2 → [(5, 7)], 3 → [(1, 3)], 4 → [ ], 5 → [(6, 2)], 6 → [(5, 5), (2, 7)], 7 → [(1, 2), (2, 6), (4, 1), (6, 4)]

7
 
0 3 0 0 0 0 5
0 0 0 0 7 0 0
 
3 0 0 0 0 0 0
 
0
ou par sa matrice d’adjacence : M =  0 0 0 0 0 0
0 0 0 0 0 2 0
 
0 7 0 0 5 0 0
2 6 0 1 0 4 0

Vocabulaire
graphe pondéré orienté : triplet (S, A, ρ) ou S est un ensemble fini non vide, A un ensemble de
couples d’éléments de S et ρ une application de S dans R+
poids d’un arc a : ρ(a)
poids d’un chemin : somme des poids des arcs de ce chemin
listes d’adjacence : pour chaque sommet s, on donne la liste des couples (t, p) où t est un sommet
où arrive un arc partant de s (successeurs de s) et p le poids de l’arc (s, t).
matrice d’adjacence : si S = J1, nK, matrice M = (mij ) ∈ Mn (R) avec mij = p si (i, j) est un arc
de poids p et mij = 0 sinon.

10 Plus court chemin, algorithme de Dijkstra


3 3 7
3 1 2 5

5 2 7 5
6 2
4
1
7 6
4

On parcours le graphe à partir du sommet 1. Pour chaque sommet s accessible à partir de 1, on détermine
le poids minimum d’un chemin joignant 1 à s.
Le graphe est donné par sa liste d’adjacence représentée par le dictionnaire :
{ 1:[(2,3),(7,5)], 2:[(5,7)], 3:[(1,3)], 4:[], 5:[(6,2)], 6:[(5,5),(2,7)],
7:[ (1,2),(2,6),(4,1),(6,4)]}
3 variables :
- aTraiter : une liste de couples (s, δ) où les s sont les sommets à traiter et δ le poids associé au
sommet s.
Au début, aTraiter est vide.
- vu : un dictionnaire qui sert à marquer si un sommet a été vu.
vu[s] vaut True si le sommet a été vu et False sinon.
Au début,
vu = { 1:False, 2:False, 3:False, 4:False, 5:False, 6:False, 7:False, }
- R : une liste qui contiendra les résultats.
Au début, R est vide.
• On commence par mettre le sommet 1 dans aTraiter avec la distance 0 :
aTraiter.append((1,0))

• Ensuite, on répète tant que aTraiter n’est pas vide :


◦ on enlève un élément (s,delta) de aTraiter ayant la plus petite distance delta.
◦ Si s n’a pas encore été vu
 on ajoute (s,delta) à R :
 on marque s comme vu
 on ajoute à aTraiter tous les voisins y = (t,r) de s qui n’ont pas encore été vus. Au
chemin t on associera le poids r+delta : aTraiter.append((y[0],delta+y[1])

8
LA = {1:[(2,3),(7,5)], 2:[(5,7)], 3:[(1,3)], 4:[], 5:[(6,2)], 6:[(5,5),(2,7)],
7:[ (1,2),(2,6),(4,1),(6,4)]}

def minDist(L):
""" L est une liste de couples (s,dist)
la fonction retourne un élément pour lequel dist est le plus petit possible"""
xmin = L[0]
min = xmin[1]
for x in L:
if x[1]<min:
xmin = x
min = xmin[1]
return xmin

def Dijkstra(listeAdj,s):
""" listeAdj est la liste d’adjacence d’un graphe pondéré
pour chaque sommet on calcule la distance minimum de s à t
on retourne le résultat dans une liste"""
#création du dictionnaire vu
vu = {}
for sommet in listeAdj:
vu[sommet] = False
# création de la liste aTraite
aTraiter = [(s,0)]
#creation de la liste des résultats
R = []
while aTraiter != []:
x = minDist(aTraiter) #élément avec une distance minimum
aTraiter = [y for y in aTraiter if y != x] # on enlève cet élément de la liste aTraiter
sommet = x[0] # sommet correspondant
if not vu[sommet]: # si ce sommet n’a pas encore été vu
R.append(x) # on l’ajoute au résultat
delta = x[1] # distance à s correspondant
vu[sommet] = True #on le marque comme vu
for y in listeAdj[sommet]: #on place dans aTraiter tous ses voisins non vus
if not vu[y[0]]:
aTraiter.append((y[0],y[1]+delta))
return R

print(Dijkstra(LA,1))

On trouve

[(1, 0), (2, 3), (7, 5), (4, 6), (6, 9), (5, 10)]

11 Une amélioration : l’algorithme A*


On cherche un chemin de longueur minimale depuis le sommet a vers le sommet b.
L’algorithme de Dijkstra va chercher les chemins à partir de a, dans toutes les directions et s’arrêtera
quand le sommet b aura été ”vu”.
On va plutôt chercher parmi les chemins qui se dirigent vers b.
On pose h : S → R+ , x 7→ kx − bk. h s’appelle une heuristique.
Ensuite, on remplace le poids ρ(x, y), de l’arc (x, y) (qui représente la distance pour aller de x à y) par
ρ0 (x, y) = ρ(x, y) + h(y) − h(x)

Vous aimerez peut-être aussi