🌐 Les Graphes
🎯 Introduction aux Graphes
📊 Qu'est-ce qu'un graphe ?
Un graphe est une structure de données fondamentale qui modélise des relations entre des objets. Il constitue l'une des abstractions les plus puissantes en informatique pour représenter des connexions complexes.
Sommets (Nœuds)
Les objets ou entités du graphe. Ils représentent les éléments que l'on souhaite connecter.
Exemples : Personnes, villes, pages web, ordinateurs...
Exemples : Personnes, villes, pages web, ordinateurs...
Arêtes (Arcs)
Les relations ou connexions entre les sommets. Elles matérialisent les liens qui unissent les objets.
Exemples : Amitiés, routes, liens hypertextes, câbles réseau...
Exemples : Amitiés, routes, liens hypertextes, câbles réseau...
🌍 Omniprésence : Les graphes sont partout en informatique : réseaux sociaux, GPS, Internet, circuits électroniques, intelligence artificielle...
🔄 Types de Graphes
Graphe Non Orienté
Les arêtes n'ont pas de direction. Si A est relié à B, alors B est automatiquement relié à A.
Exemple : Réseau d'amitié (relation symétrique)
A ---- B | | C ---- D
Graphe Orienté
Les arêtes ont une direction précise (représentée par des flèches).
Exemple : Réseau de followers (relation asymétrique)
A ---> B ^ | C <--- D
Graphe Pondéré
Les arêtes possèdent un poids (coût, distance, temps, capacité...).
Exemple : Réseau routier avec distances
A --5-- B | | 3 2 | | C --1-- D
💾 Représentations en Mémoire
🎯 Choix de représentation
Le choix de la représentation dépend de la densité du graphe et des opérations les plus fréquentes. Chaque approche a ses avantages selon le contexte d'utilisation.
📊 Matrice d'Adjacence
Tableau 2D où
matrice[i][j]
indique s'il y a une arête entre le sommet i et le sommet j.
💻 Exemple de matrice
# Graphe A-B-C avec A relié à B et C
matrice = [
[0, 1, 1], # A : relié à B(1) et C(2)
[1, 0, 0], # B : relié à A(0)
[1, 0, 0] # C : relié à A(0)
]
✅ Avantages :
- Vérification d'adjacence en O(1)
- Simple à implémenter
- Idéal pour les graphes denses
- Espace O(n²) même pour graphes peu denses
- Parcours des voisins en O(n)
📋 Liste d'Adjacence
Chaque sommet maintient une liste de ses voisins directs.
💻 Exemple de liste
# Même graphe en liste d'adjacence
graphe = {
'A': ['B', 'C'],
'B': ['A'],
'C': ['A']
}
✅ Avantages :
- Espace O(n + m) où m = nombre d'arêtes
- Parcours des voisins efficace
- Idéal pour les graphes peu denses
- Vérification d'adjacence en O(degré)
- Plus complexe à implémenter
Opération | Matrice d'Adjacence | Liste d'Adjacence | Recommandation |
---|---|---|---|
Vérifier adjacence | O(1) | O(degré) | Matrice pour fréquent |
Parcourir voisins | O(n) | O(degré) | Liste plus efficace |
Espace mémoire | O(n²) | O(n + m) | Liste pour graphes peu denses |
Ajouter arête | O(1) | O(1) | Équivalent |
🏗️ Implémentation en Python
🎯 Classe Graphe Complète
Implémentation robuste utilisant les listes d'adjacence avec support des graphes orientés/non-orientés et pondérés.
Structure de base
Initialisation et gestion des sommets avec flexibilité d'orientation.
💻 Constructeur et ajout de sommets
class Graphe:
def __init__(self, oriente=False):
self.sommets = {} # dictionnaire : sommet -> liste des voisins
self.oriente = oriente
def ajouter_sommet(self, sommet):
"""Ajoute un sommet au graphe"""
if sommet not in self.sommets:
self.sommets[sommet] = []
Gestion des arêtes
Ajout et suppression d'arêtes avec gestion automatique de l'orientation.
💻 Méthodes d'arêtes
def ajouter_arete(self, sommet1, sommet2, poids=1):
"""Ajoute une arête entre deux sommets"""
self.ajouter_sommet(sommet1)
self.ajouter_sommet(sommet2)
# Ajouter l'arête
self.sommets[sommet1].append((sommet2, poids))
# Si non orienté, ajouter l'arête inverse
if not self.oriente:
self.sommets[sommet2].append((sommet1, poids))
🔍 Méthodes d'analyse
Fonctions utilitaires pour analyser la structure du graphe.
💻 Analyse et vérification
def voisins(self, sommet):
"""Retourne la liste des voisins d'un sommet"""
return self.sommets.get(sommet, [])
def est_adjacent(self, sommet1, sommet2):
"""Vérifie si deux sommets sont adjacents"""
return any(voisin == sommet2 for voisin, _ in self.voisins(sommet1))
def degre(self, sommet):
"""Retourne le degré d'un sommet"""
return len(self.voisins(sommet))
🗑️ Suppression d'arêtes
Gestion de la suppression avec respect de l'orientation.
💻 Suppression intelligente
def supprimer_arete(self, sommet1, sommet2):
"""Supprime une arête"""
if sommet1 in self.sommets:
self.sommets[sommet1] = [(v, p) for v, p in self.sommets[sommet1]
if v != sommet2]
if not self.oriente and sommet2 in self.sommets:
self.sommets[sommet2] = [(v, p) for v, p in self.sommets[sommet2]
if v != sommet1]
🚀 Exemple d'utilisation pratique
💻 Construction et manipulation
# Création d'un graphe non orienté
g = Graphe(oriente=False)
# Ajout des arêtes avec pondération
g.ajouter_arete('A', 'B', 5)
g.ajouter_arete('A', 'C', 3)
g.ajouter_arete('B', 'D', 2)
g.ajouter_arete('C', 'D', 1)
# Analyse du graphe
print(f"Degré de A: {g.degre('A')}") # 2
print(f"A et D adjacents: {g.est_adjacent('A', 'D')}") # False
print(f"Voisins de B: {g.voisins('B')}") # [('A', 5), ('D', 2)]
💡 Flexibilité : Cette implémentation supporte automatiquement les graphes orientés, non-orientés et pondérés selon les paramètres fournis.
🔍 Algorithmes de Parcours
🎯 Stratégies d'exploration
Les algorithmes de parcours permettent d'explorer systématiquement tous les sommets d'un graphe. Chaque stratégie a ses propres caractéristiques et applications spécifiques.
📊 Parcours en Largeur (BFS)
Explore le graphe niveau par niveau en utilisant une file (FIFO). Idéal pour trouver le plus court chemin.
🎯 Principe : Visiter tous les voisins directs avant d'explorer plus loin
💻 Implémentation BFS
from collections import deque
def parcours_largeur(graphe, sommet_depart):
"""Parcours en largeur (BFS)"""
visites = set()
file = deque([sommet_depart])
ordre_visite = []
while file:
sommet = file.popleft() # FIFO
if sommet not in visites:
visites.add(sommet)
ordre_visite.append(sommet)
# Ajouter les voisins non visités
for voisin, _ in graphe.voisins(sommet):
if voisin not in visites:
file.append(voisin)
return ordre_visite
✅ Applications :
- Plus court chemin (non pondéré)
- Détection de composantes connexes
- Analyse de réseaux sociaux
🔄 Parcours en Profondeur (DFS)
Explore le graphe en allant le plus loin possible avant de revenir en arrière. Utilise une pile (LIFO) ou la récursion.
🎯 Principe : Aller au bout d'un chemin avant d'explorer les alternatives
💻 Version récursive
def parcours_profondeur_recursif(graphe, sommet, visites=None):
"""Parcours en profondeur récursif (DFS)"""
if visites is None:
visites = set()
visites.add(sommet)
print(sommet, end=' ')
for voisin, _ in graphe.voisins(sommet):
if voisin not in visites:
parcours_profondeur_recursif(graphe, voisin, visites)
💻 Version itérative
def parcours_profondeur_iteratif(graphe, sommet_depart):
"""Parcours en profondeur itératif (DFS)"""
visites = set()
pile = [sommet_depart]
ordre_visite = []
while pile:
sommet = pile.pop() # LIFO
if sommet not in visites:
visites.add(sommet)
ordre_visite.append(sommet)
# Ajouter les voisins (ordre inverse)
for voisin, _ in reversed(graphe.voisins(sommet)):
if voisin not in visites:
pile.append(voisin)
return ordre_visite
✅ Applications :
- Détection de cycles
- Tri topologique
- Résolution de labyrinthes
Critère | BFS (Largeur) | DFS (Profondeur) | Recommandation |
---|---|---|---|
Structure de données | File (FIFO) | Pile (LIFO) / Récursion | Selon l'objectif |
Complexité temporelle | O(V + E) | O(V + E) | Équivalent |
Complexité spatiale | O(V) | O(h) où h = hauteur | DFS pour économiser l'espace |
Plus court chemin | ✅ Optimal | ❌ Non optimal | BFS pour distances |
Détection de cycles | ✅ Possible | ✅ Plus naturel | DFS plus simple |
🧠 Choix stratégique : BFS pour l'exploration systématique et les distances, DFS pour l'analyse structurelle et la détection de motifs.
🧮 Algorithmes Avancés sur les Graphes
🎯 Algorithmes fondamentaux
Ces algorithmes résolvent des problèmes classiques sur les graphes : détection de cycles, calcul de plus courts chemins, et analyse de connectivité.
🔄 Détection de Cycle
Algorithme utilisant DFS pour détecter la présence de cycles dans un graphe non orienté.
💡 Principe : Un cycle existe si on rencontre un sommet déjà visité qui n'est pas le parent direct
💻 Implémentation détection de cycle
def a_un_cycle(graphe):
"""Détecte s'il y a un cycle dans un graphe non orienté"""
visites = set()
def dfs(sommet, parent):
visites.add(sommet)
for voisin, _ in graphe.voisins(sommet):
if voisin not in visites:
if dfs(voisin, sommet):
return True
elif voisin != parent: # Cycle détecté !
return True
return False
# Vérifier chaque composante connexe
for sommet in graphe.sommets:
if sommet not in visites:
if dfs(sommet, None):
return True
return False
🛣️ Algorithme de Dijkstra
Calcule le plus court chemin depuis un sommet source vers tous les autres sommets dans un graphe pondéré.
⚡ Complexité : O((V + E) log V) avec une file de priorité
🎯 Algorithme principal
💻 Dijkstra complet
import heapq
def dijkstra(graphe, depart):
"""Algorithme de Dijkstra pour le plus court chemin"""
distances = {sommet: float('inf') for sommet in graphe.sommets}
distances[depart] = 0
predecesseurs = {}
# File de priorité : (distance, sommet)
file_priorite = [(0, depart)]
while file_priorite:
distance_actuelle, sommet_actuel = heapq.heappop(file_priorite)
# Optimisation : ignorer si déjà traité
if distance_actuelle > distances[sommet_actuel]:
continue
# Examiner tous les voisins
for voisin, poids in graphe.voisins(sommet_actuel):
nouvelle_distance = distance_actuelle + poids
if nouvelle_distance < distances[voisin]:
distances[voisin] = nouvelle_distance
predecesseurs[voisin] = sommet_actuel
heapq.heappush(file_priorite, (nouvelle_distance, voisin))
return distances, predecesseurs
🔄 Reconstruction de chemin
💻 Récupération du chemin
def reconstruire_chemin(predecesseurs, depart, arrivee):
"""Reconstruit le chemin à partir des prédécesseurs"""
chemin = []
sommet = arrivee
while sommet is not None:
chemin.append(sommet)
sommet = predecesseurs.get(sommet)
chemin.reverse()
return chemin if chemin[0] == depart else []
# Exemple d'utilisation
distances, predecesseurs = dijkstra(graphe, 'A')
chemin_vers_D = reconstruire_chemin(predecesseurs, 'A', 'D')
print(f"Plus court chemin A→D: {chemin_vers_D}")
print(f"Distance: {distances['D']}")
🚀 Performance : Dijkstra est optimal pour les graphes avec poids positifs. Pour les poids négatifs, utiliser Bellman-Ford.
🌍 Applications Pratiques des Graphes
🎯 Domaines d'application
Les graphes modélisent de nombreux systèmes réels : réseaux sociaux, systèmes de navigation, réseaux informatiques, analyse de données, etc.
Réseaux Sociaux
Modélisation : Utilisateurs = sommets, Relations = arêtes
Algorithmes : Suggestions d'amis, communautés, influence
Algorithmes : Suggestions d'amis, communautés, influence
Navigation GPS
Modélisation : Intersections = sommets, Routes = arêtes pondérées
Algorithmes : Plus court chemin, optimisation de trajets
Algorithmes : Plus court chemin, optimisation de trajets
Réseaux Informatiques
Modélisation : Routeurs = sommets, Connexions = arêtes
Algorithmes : Routage, détection de pannes, optimisation
Algorithmes : Routage, détection de pannes, optimisation
Analyse de Données
Modélisation : Entités = sommets, Relations = arêtes
Algorithmes : Clustering, classification, recommandations
Algorithmes : Clustering, classification, recommandations
👥 Exemple : Réseau Social
💻 Implémentation réseau social
class ReseauSocial:
def __init__(self):
self.graphe = Graphe(oriente=False)
def ajouter_personne(self, nom):
self.graphe.ajouter_sommet(nom)
def ajouter_amitie(self, personne1, personne2):
self.graphe.ajouter_arete(personne1, personne2)
def amis_communs(self, personne1, personne2):
"""Trouve les amis communs entre deux personnes"""
amis1 = {voisin for voisin, _ in self.graphe.voisins(personne1)}
amis2 = {voisin for voisin, _ in self.graphe.voisins(personne2)}
return amis1.intersection(amis2)
def suggestions_amis(self, personne):
"""Suggère des amis basé sur les amis d'amis"""
amis_directs = {voisin for voisin, _ in self.graphe.voisins(personne)}
suggestions = set()
for ami in amis_directs:
for ami_dami, _ in self.graphe.voisins(ami):
if ami_dami != personne and ami_dami not in amis_directs:
suggestions.add(ami_dami)
return suggestions
def degre_separation(self, personne1, personne2):
"""Calcule le degré de séparation (distance) entre deux personnes"""
distances, _ = dijkstra(self.graphe, personne1)
return distances.get(personne2, float('inf'))
💡 Algorithme de suggestion : Basé sur le principe "les amis de mes amis sont mes amis potentiels"
🗺️ Exemple : Système de Navigation
💻 Implémentation GPS
class CarteRoutiere:
def __init__(self):
self.graphe = Graphe(oriente=False)
def ajouter_route(self, ville1, ville2, distance):
self.graphe.ajouter_arete(ville1, ville2, distance)
def plus_court_chemin(self, depart, arrivee):
"""Trouve le plus court chemin entre deux villes"""
distances, predecesseurs = dijkstra(self.graphe, depart)
if distances[arrivee] == float('inf'):
return None, float('inf')
chemin = reconstruire_chemin(predecesseurs, depart, arrivee)
return chemin, distances[arrivee]
def itineraire_complet(self, depart, arrivee):
"""Retourne un itinéraire détaillé"""
chemin, distance = self.plus_court_chemin(depart, arrivee)
if chemin is None:
return None
return {
'chemin': chemin,
'distance_totale': distance,
'etapes': len(chemin) - 1,
'villes_traversees': len(chemin)
}
# Exemple d'utilisation
carte = CarteRoutiere()
carte.ajouter_route("Paris", "Lyon", 465)
carte.ajouter_route("Lyon", "Marseille", 315)
itineraire = carte.itineraire_complet("Paris", "Marseille")
if itineraire:
print(f"Trajet: {' → '.join(itineraire['chemin'])}")
print(f"Distance: {itineraire['distance_totale']} km")
🚗 Optimisation réelle : Les GPS modernes intègrent trafic, péages, préférences utilisateur
⚡ Analyse de Complexité
📊 Comparaison des performances
Le choix de la représentation impacte directement les performances selon les opérations effectuées.
Opération | Matrice d'adjacence | Liste d'adjacence | Meilleur choix |
---|---|---|---|
Ajouter sommet | O(n²) | O(1) | Liste d'adjacence |
Ajouter arête | O(1) | O(1) | Équivalent |
Supprimer arête | O(1) | O(degré) | Matrice d'adjacence |
Vérifier adjacence | O(1) | O(degré) | Matrice d'adjacence |
Parcours complet | O(n²) | O(n + m) | Liste pour graphes peu denses |
Espace mémoire | O(n²) | O(n + m) | Liste pour graphes peu denses |
Notation Big O
n = nombre de sommets
m = nombre d'arêtes
degré = nombre de voisins d'un sommet
V = vertices (sommets)
E = edges (arêtes)
m = nombre d'arêtes
degré = nombre de voisins d'un sommet
V = vertices (sommets)
E = edges (arêtes)
Règles de choix
Matrice : Graphes denses, vérifications fréquentes
Liste : Graphes peu denses, parcours fréquents
Seuil : m ≈ n²/2 pour choisir
Liste : Graphes peu denses, parcours fréquents
Seuil : m ≈ n²/2 pour choisir
🎓 Synthèse et Conclusion
🌟 Les graphes : une abstraction fondamentale
Les graphes constituent l'une des structures de données les plus polyvalentes en informatique, permettant de modéliser et résoudre une multitude de problèmes complexes.
Concepts Fondamentaux
• Structure : Sommets + Arêtes
• Types : Orienté/Non-orienté, Pondéré
• Représentations : Matrice vs Liste
• Flexibilité : Adaptation au contexte
• Types : Orienté/Non-orienté, Pondéré
• Représentations : Matrice vs Liste
• Flexibilité : Adaptation au contexte
Algorithmes Essentiels
• Parcours : BFS (largeur) et DFS (profondeur)
• Chemins : Dijkstra pour plus courts chemins
• Analyse : Détection de cycles, connectivité
• Complexité : O(V + E) pour la plupart
• Chemins : Dijkstra pour plus courts chemins
• Analyse : Détection de cycles, connectivité
• Complexité : O(V + E) pour la plupart
Applications Réelles
• Réseaux sociaux : Relations, suggestions
• Navigation : GPS, optimisation de trajets
• Internet : Routage, pages web
• IA : Recherche, planification
• Navigation : GPS, optimisation de trajets
• Internet : Routage, pages web
• IA : Recherche, planification
Choix Stratégiques
• Densité : Critère de choix principal
• Opérations : Fréquence d'utilisation
• Mémoire : Contraintes d'espace
• Performance : Optimisation ciblée
• Opérations : Fréquence d'utilisation
• Mémoire : Contraintes d'espace
• Performance : Optimisation ciblée
🎯 Points Clés à Retenir
🧠 Modélisation : Identifier les entités (sommets) et leurs relations (arêtes) est la première étape cruciale
⚡ Performance : Le choix matrice vs liste dépend de la densité : liste pour graphes peu denses (m << n²)
🔍 Algorithmes : BFS pour distances minimales, DFS pour exploration exhaustive et détection de cycles
🌐 Universalité : Les graphes sont omniprésents : du GPS aux réseaux sociaux, en passant par l'IA
🚀 Évolutivité : Les algorithmes sur graphes s'adaptent naturellement aux gros volumes de données
🔮 Perspectives d'approfondissement
Algorithmes avancés : Bellman-Ford, Floyd-Warshall, Kruskal, Prim
Graphes spécialisés : Arbres, DAG, graphes bipartites
Applications avancées : Machine Learning, analyse de réseaux, optimisation
Graphes spécialisés : Arbres, DAG, graphes bipartites
Applications avancées : Machine Learning, analyse de réseaux, optimisation